def get_note_topics(
    self,
    notes: pd.DataFrame,
    noteTopicClassifiers: Optional[List[Pipeline]] = None,
    seedLabelSets: Optional[List[np.ndarray]] = None,
    conflictedTextSetsForAccuracyEval: Optional[List[np.ndarray]] = None,
    bootstrapped: Optional[bool] = False,
    assignConflicted: Optional[bool] = False,
    exitOnLowAccuracy: Optional[bool] = True,
  ) -> pd.DataFrame:
    """Return a DataFrame specifying each {note, topic} pair.

    Notes that are not assigned to a topic do not appear in the dataframe.

    Args:
      notes: DF containing all notes to potentially assign to a topic
    """
    logger.info("Assigning notes to topics:")
    if noteTopicClassifiers is not None:
      pipes = noteTopicClassifiers
    else:
      logger.info("Training note topic classifier")
      if bootstrapped:
        (
          pipes,
          seedLabelSets,
          conflictedTextSetsForAccuracyEval,
        ) = self.train_bootstrapped_note_topic_classifier(notes)
      else:
        (
          pipe,
          seedLabelSet,
          conflictedTextForAccuracyEval,
        ) = self.train_note_topic_classifier(notes)
        pipes, seedLabelSets, conflictedTextSetsForAccuracyEval = (
          [pipe],
          [seedLabelSet],
          [conflictedTextForAccuracyEval],
        )
    with c.time_block("Get Note Topics: Prepare Post Text"):
      postText = self._prepare_post_text(notes)

    labelSets = []
    if seedLabelSets is None:
      seedLabelSets = [None for i in range(len(pipes))]
    if conflictedTextSetsForAccuracyEval is None:
      conflictedTextSetsForAccuracyEval = [None for i in range(len(pipes))]

    with c.time_block("Get Note Topics: Predict"):
      # Predict notes.  Notice that in effect we are looking to see which notes in the
      # training data the model felt were mis-labeled after the training process
      # completed, and generating labels for any posts which were omitted from the
      # original training.
      for i, pipe in enumerate(pipes):
        assert type(pipe) == Pipeline, "unsupported classifier"
        logits = pipe.decision_function(postText[c.summaryKey].values)
        # Transform logits to probabilities, handling the case where logits are 1D because
        # of unit testing with only 2 topics.
        if len(logits.shape) == 1:
          probs = sigmoid(logits)
          probs = np.vstack([1 - probs, probs]).T
        else:
          probs = softmax(logits, axis=1)

        if seedLabelSets[i] is None:
          with c.time_block("Get Note Topics: Make Seed Labels"):
            seedLabelSets[i], _ = self._make_seed_labels(postText[c.summaryKey].values)

        if conflictedTextSetsForAccuracyEval[i] is not None:
          self.validate_note_topic_accuracy_on_seed_labels(
            np.argmax(probs, axis=1),
            seedLabelSets[i],
            conflictedTextSetsForAccuracyEval[i],
            exitOnLowAccuracy,
          )

        with c.time_block("Get Note Topics: Merge and assign predictions"):
          topicAssignments = self._merge_predictions_and_labels(probs, seedLabelSets[i])
          logger.info(f"  Post Topic assignment results: {np.bincount(topicAssignments)}")

          # Assign topics to notes based on aggregated note text, and drop any
          # notes on posts that were unassigned.
          postTextCopy = postText.copy()
          postTextCopy[c.noteTopicKey] = [Topics(t).name for t in topicAssignments]
          postTextCopy = postTextCopy[postTextCopy[c.noteTopicKey] != Topics.Unassigned.name]
          noteTopics = notes[[c.noteIdKey, c.tweetIdKey]].merge(
            postTextCopy[[c.tweetIdKey, c.noteTopicKey]]
          )
          print(noteTopics.shape)
          labelSets.append(noteTopics.drop(columns=c.tweetIdKey))
        logger.info(
          f"  Note Topic assignment results:\n{noteTopics[c.noteTopicKey].value_counts(dropna=False)}"
        )
    if len(labelSets) == 1:
      return noteTopics.drop(columns=c.tweetIdKey)
    else:
      noteTopics = pd.concat(labelSets)
      noteTopics["cnt"] = 1
      numTopics = (
        noteTopics[[c.noteIdKey, c.noteTopicKey]]
        .drop_duplicates()
        .groupby([c.noteIdKey])
        .agg("count")
        .reset_index()
      )
      conflicted = numTopics.loc[numTopics[c.noteTopicKey] > 1]
      if assignConflicted == True:
        # assign to most common result
        return (
          noteTopics.groupby([c.noteIdKey, c.noteTopicKey])
          .agg("count")
          .reset_index()
          .sort_values("cnt", ascending=False)
          .groupby(c.noteIdKey)
          .head(1)
          .drop(columns="cnt")
        )
      else:
        return (
          noteTopics.loc[~(noteTopics[c.noteIdKey].isin(conflicted[c.noteIdKey].values))]
          .groupby([c.noteIdKey, c.noteTopicKey])
          .head(1)
          .drop(columns="cnt")
        )
Return a DataFrame specifying each {note, topic} pair.

    Notes that are not assigned to a topic do not appear in the dataframe.

    Args:
      notes: DF containing all notes to potentially assign to a topic
    
sourcecode/scoring/topic_model.py