class_docstr: Python impl of TextRank by Milhacea, et al., as a spaCy extension,
  used to extract the top-ranked phrases from a text document
class_docstr_tok: ["Python","impl","of","TextRank","by","Milhacea",",","et","al.",",","as","a","spaCy","extension",",","used","to","extract","the","top-ranked","phrases","from","a","text","document"]
docstr: iterate through each sentence in the doc, constructing a lemma graph
  then returning the top-ranked phrases
docstr_tok: ["iterate","through","each","sentence","in","the","doc",",","constructing","a","lemma","graph","then","returning","the","top-ranked","phrases"]
code: def calc_textrank(self):
      """
      iterate through each sentence in the doc, constructing a lemma graph
      then returning the top-ranked phrases
      """
      self.reset()
      t0 = time.time()
  
      for sent in self.doc.sents:
          self.link_sentence(sent)
  
      if self.logger:
          self.logger.debug(self.seen_lemma)
  
      # to run the algorithm, we use the NetworkX implementation of
      # PageRank – i.e., approximating eigenvalue centrality – to
      # calculate ranks for each of the nodes in the lemma graph
  
      self.ranks = nx.pagerank(self.lemma_graph)
  
      # collect the top-ranked phrases based on both the noun chunks
      # and the named entities
  
      for chunk in self.doc.noun_chunks:
          self.collect_phrases(chunk)
  
      for ent in self.doc.ents:
          self.collect_phrases(ent)
  
      # TODO:
      # there are edge cases where the built-in noun chunking in
      # spaCy fails to extract much, for example:
  
      # > "everything you need to know about student loan interest rates variable and fixed rates capitalization amortization student loan refinancing and more."
  
      # we should test `len(self.phrases.keys())` vs. a brute-force
      # noun chunking approach, then add the brute-force override to
      # `self.phrases`
  
      # for k, p in self.phrases.items():
      #    print(" >>", k, p)
  
      # for key, lemma in self.seen_lemma.items():
      #    node_id = list(self.seen_lemma.keys()).index(key)
      #    print(node_id, key, lemma, self.ranks[node_id])
  
      # since noun chunks can be expressed in different ways (e.g., may
      # have articles or prepositions), we need to find a minimum span
      # for each phrase based on combinations of lemmas
  
      min_phrases = {}
  
      for phrase_key, phrase_list in self.phrases.items():
          phrase_list.sort(key=lambda p: p.rank, reverse=True)
          best_phrase = phrase_list[0]
          min_phrases[best_phrase.text] = (
              best_phrase.rank,
              len(phrase_list),
              phrase_key,
          )
  
      # yield results
  
      results = sorted(
          min_phrases.items(), key=lambda x: x[1][0], reverse=True
      )
  
      phrase_list = [
          Phrase(p, r, c, self.phrases[k]) for p, (r, c, k) in results
      ]
  
      t1 = time.time()
      self.elapsed_time = (t1 - t0) * 1000.0
  
      return phrase_list
  
imports: from .base import BaseTextRank
  from .positionrank import PositionRank
  from collections import defaultdict, OrderedDict
  from math import sqrt
  from spacy.tokens import Doc
  import graphviz
  import json
  import logging
  import networkx
  import os
  import os.path
  import re
  import spacy
  import string
  import sys
  import time
  import unicodedata
label_desc: Use image node_id 8 for calling the TextRank obj's underlying member method code with example usage: obj.calc_textrank() and returns: phrase_list