code: def _find_optimal_rank(self, ranks_df):
      max_rank = ranks_df.max().max()
      min_rank = 1
      last_max_rank = None
      last_min_rank = None
      while max_rank - 1 > min_rank:
          if last_max_rank is not None:
              if (
                  last_min_rank == min_rank
                  and last_max_rank == max_rank
              ):
                  raise Exception(
                      "Error. Potential infinite loop detected."
                  )
          last_max_rank = max_rank
          last_min_rank = min_rank
          cur_rank = int((max_rank - min_rank) / 2) + min_rank
          num_terms = self._get_num_terms_at_rank(cur_rank, ranks_df)
          if num_terms > self.max_terms:
              max_rank = cur_rank
          elif num_terms < self.max_terms:
              min_rank = cur_rank
          else:
              return cur_rank
      return min_rank
  
imports: from typing import Optional, Tuple
  import pandas
  import numpy
  from scattertext.termscoring.CorpusBasedTermScorer import CorpusBasedTermScorer
  from scipy.stats import rankdata
  from scattertext.termranking.AbsoluteFrequencyRanker import AbsoluteFrequencyRanker
  from scattertext.termscoring.ScaledFScore import ScaledFScorePresetsNeg1To1
label_desc: Use image node_id 4 for calling the AssociationCompactor obj's underlying member method code with example usage: obj._find_optimal_rank(ranks_df) and returns: min_rank, cur_rank