docs: def extended_stats(G, connectivity=False, anc=False, ecc=False, bc=False, cc=False):
      """
      Calculate extended topological stats and metrics for a graph.
  
      Many of these algorithms have an inherently high time complexity. Global
      topological analysis of large complex networks is extremely time consuming
      and may exhaust computer memory. Consider using function arguments to not
      run metrics that require computation of a full matrix of paths if they
      will not be needed.
  
      Parameters
      ----------
      G : networkx multidigraph
      connectivity : bool
          if True, calculate node and edge connectivity
      anc : bool
          if True, calculate average node connectivity
      ecc : bool
          if True, calculate shortest paths, eccentricity, and topological metrics
          that use eccentricity
      bc : bool
          if True, calculate node betweenness centrality
      cc : bool
          if True, calculate node closeness centrality
  
      Returns
      -------
      stats : dict
          dictionary of network measures containing the following elements (some
          only calculated/returned optionally, based on passed parameters):
  
            - avg_neighbor_degree
            - avg_neighbor_degree_avg
            - avg_weighted_neighbor_degree
            - avg_weighted_neighbor_degree_avg
            - degree_centrality
            - degree_centrality_avg
            - clustering_coefficient
            - clustering_coefficient_avg
            - clustering_coefficient_weighted
            - clustering_coefficient_weighted_avg
            - pagerank
            - pagerank_max_node
            - pagerank_max
            - pagerank_min_node
            - pagerank_min
            - node_connectivity
            - node_connectivity_avg
            - edge_connectivity
            - eccentricity
            - diameter
            - radius
            - center
            - periphery
            - closeness_centrality
            - closeness_centrality_avg
            - betweenness_centrality
            - betweenness_centrality_avg
  
      """
  
      stats = {}
      full_start_time = time.time()
  
      # create a DiGraph from the MultiDiGraph, for those metrics that require it
      G_dir = nx.DiGraph(G)
  
      # create an undirected Graph from the MultiDiGraph, for those metrics that
      # require it
      G_undir = nx.Graph(G)
  
      # get the largest strongly connected component, for those metrics that
      # require strongly connected graphs
      G_strong = get_largest_component(G, strongly=True)
  
      # average degree of the neighborhood of each node, and average for the graph
      avg_neighbor_degree = nx.average_neighbor_degree(G)
      stats['avg_neighbor_degree'] = avg_neighbor_degree
      stats['avg_neighbor_degree_avg'] = sum(avg_neighbor_degree.values())/len(avg_neighbor_degree)
  
      # average weighted degree of the neighborhood of each node, and average for
      # the graph
      avg_weighted_neighbor_degree = nx.average_neighbor_degree(G, weight='length')
      stats['avg_weighted_neighbor_degree'] = avg_weighted_neighbor_degree
      stats['avg_weighted_neighbor_degree_avg'] = sum(avg_weighted_neighbor_degree.values())/len(avg_weighted_neighbor_degree)
  
      # degree centrality for a node is the fraction of nodes it is connected to
      degree_centrality = nx.degree_centrality(G)
      stats['degree_centrality'] = degree_centrality
      stats['degree_centrality_avg'] = sum(degree_centrality.values())/len(degree_centrality)
  
      # calculate clustering coefficient for the nodes
      stats['clustering_coefficient'] = nx.clustering(G_undir)
  
      # average clustering coefficient for the graph
      stats['clustering_coefficient_avg'] = nx.average_clustering(G_undir)
  
      # calculate weighted clustering coefficient for the nodes
      stats['clustering_coefficient_weighted'] = nx.clustering(G_undir, weight='length')
  
      # average clustering coefficient (weighted) for the graph
      stats['clustering_coefficient_weighted_avg'] = nx.average_clustering(G_undir, weight='length')
  
      # pagerank: a ranking of the nodes in the graph based on the structure of
      # the incoming links
      pagerank = nx.pagerank(G_dir, weight='length')
      stats['pagerank'] = pagerank
  
      # node with the highest page rank, and its value
      pagerank_max_node = max(pagerank, key=lambda x: pagerank[x])
      stats['pagerank_max_node'] = pagerank_max_node
      stats['pagerank_max'] = pagerank[pagerank_max_node]
  
      # node with the lowest page rank, and its value
      pagerank_min_node = min(pagerank, key=lambda x: pagerank[x])
      stats['pagerank_min_node'] = pagerank_min_node
      stats['pagerank_min'] = pagerank[pagerank_min_node]
  
      # if True, calculate node and edge connectivity
      if connectivity:
          start_time = time.time()
  
          # node connectivity is the minimum number of nodes that must be removed
          # to disconnect G or render it trivial
          stats['node_connectivity'] = nx.node_connectivity(G_strong)
  
          # edge connectivity is equal to the minimum number of edges that must be
          # removed to disconnect G or render it trivial
          stats['edge_connectivity'] = nx.edge_connectivity(G_strong)
          log('Calculated node and edge connectivity in {:,.2f} seconds'.format(time.time() - start_time))
  
      # if True, calculate average node connectivity
      if anc:
          # mean number of internally node-disjoint paths between each pair of
          # nodes in G, i.e., the expected number of nodes that must be removed to
          # disconnect a randomly selected pair of non-adjacent nodes
          start_time = time.time()
          stats['node_connectivity_avg'] = nx.average_node_connectivity(G)
          log('Calculated average node connectivity in {:,.2f} seconds'.format(time.time() - start_time))
  
      # if True, calculate shortest paths, eccentricity, and topological metrics
      # that use eccentricity
      if ecc:
          # precompute shortest paths between all nodes for eccentricity-based
          # stats
          start_time = time.time()
          sp = {source:dict(nx.single_source_dijkstra_path_length(G_strong, source, weight='length')) for source in G_strong.nodes()}
  
          log('Calculated shortest path lengths in {:,.2f} seconds'.format(time.time() - start_time))
  
          # eccentricity of a node v is the maximum distance from v to all other
          # nodes in G
          eccentricity = nx.eccentricity(G_strong, sp=sp)
          stats['eccentricity'] = eccentricity
  
          # diameter is the maximum eccentricity
          diameter = nx.diameter(G_strong, e=eccentricity)
          stats['diameter'] = diameter
  
          # radius is the minimum eccentricity
          radius = nx.radius(G_strong, e=eccentricity)
          stats['radius'] = radius
  
          # center is the set of nodes with eccentricity equal to radius
          center = nx.center(G_strong, e=eccentricity)
          stats['center'] = center
  
          # periphery is the set of nodes with eccentricity equal to the diameter
          periphery = nx.periphery(G_strong, e=eccentricity)
          stats['periphery'] = periphery
  
      # if True, calculate node closeness centrality
      if cc:
          # closeness centrality of a node is the reciprocal of the sum of the
          # shortest path distances from u to all other nodes
          start_time = time.time()
          closeness_centrality = nx.closeness_centrality(G, distance='length')
          stats['closeness_centrality'] = closeness_centrality
          stats['closeness_centrality_avg'] = sum(closeness_centrality.values())/len(closeness_centrality)
          log('Calculated closeness centrality in {:,.2f} seconds'.format(time.time() - start_time))
  
      # if True, calculate node betweenness centrality
      if bc:
          # betweenness centrality of a node is the sum of the fraction of
          # all-pairs shortest paths that pass through node
          start_time = time.time()
          betweenness_centrality = nx.betweenness_centrality(G, weight='length')
          stats['betweenness_centrality'] = betweenness_centrality
          stats['betweenness_centrality_avg'] = sum(betweenness_centrality.values())/len(betweenness_centrality)
          log('Calculated betweenness centrality in {:,.2f} seconds'.format(time.time() - start_time))
  
      log('Calculated extended stats in {:,.2f} seconds'.format(time.time()-full_start_time))
      return stats