def METHOD_NAME(
        centroids,
        neighbor_distances,
        neighbor_counts,
):
    """Internal support for compute_global_cell_graph_features that
    returns its result in a nested nametuple structure instead of a
    pandas DataFrame.
    """
    from scipy import sparse
    from scipy.sparse.csgraph import minimum_spanning_tree
    from scipy.spatial import Voronoi
    from scipy.spatial import cKDTree as KDTree
    from scipy.spatial.distance import pdist
    vor = Voronoi(centroids)
    centroids = vor.points
    vertices = vor.vertices
    regions = [r for r in vor.regions if r and -1 not in r]
    areas = np.array([_poly_area(vertices[r]) for r in regions])
    peris = np.array([_poly_peri(vertices[r]) for r in regions])
    max_dists = np.array([pdist(vertices[r]).max() for r in regions])
    poly_props = PolyProps._make(map(_pop_stats, (areas, peris, max_dists)))
    # Assume that each Voronoi vertex is on exactly three ridges.
    ridge_points = vor.ridge_points
    # This isn't exactly the collection of sides, since if they should
    # be counted per-triangle then we weight border ridges wrong
    # relative to ridges that are part of two triangles.
    ridge_lengths = _dist(*np.swapaxes(centroids[ridge_points], 0, 1))
    sides = ridge_lengths
    # Point indices of each triangle
    tris = [[] for _ in vertices]
    for p, ri in enumerate(vor.point_region):
        for vi in vor.regions[ri]:
            if vi != -1:
                tris[vi].append(p)
    # This will only fail in particular symmetrical cases where a
    # Voronoi vertex is associated with more than three centroids.
    # Since this should be unlikely in practice, we don't handle it
    # beyond throwing an AssertionError
    assert all(len(t) == 3 for t in tris)
    tris = np.asarray(tris)
    areas = np.array([_poly_area(centroids[t]) for t in tris])
    tri_props = TriProps._make(map(_pop_stats, (sides, areas)))
    graph = sparse.coo_matrix((ridge_lengths, np.sort(ridge_points).T),
                              (len(centroids), len(centroids)))
    mst = minimum_spanning_tree(graph)
    # Without looking into exactly how minimum_spanning_tree
    # constructs its output, elimate any explicit zeros to be on the
    # safe side.
    mst_branches = _pop_stats(mst.data[mst.data != 0])
    tree = KDTree(centroids)
    neighbors_in_distance = {
        # Yes, we just throw away the actual points
        r: _pop_stats(np.stack(np.array([
            m for m in map(len, tree.query_ball_tree(tree, r))])) - 1)
        for r in neighbor_distances
    }
    distance_for_neighbors = dict(zip(
        neighbor_counts,
        map(_pop_stats, tree.query(centroids, [c + 1 for c in neighbor_counts])[0].T),
    ))
    density_props = DensityProps(neighbors_in_distance, distance_for_neighbors)
    return Props(poly_props, tri_props, mst_branches, density_props)