示例#1
0
 def test_weighted_character_array(self):
     area = self.df_buildings.geometry.area
     sw = Queen_higher(k=3, geodataframe=self.df_tessellation)
     weighted = mm.weighted_character(self.df_buildings,
                                      self.df_tessellation, 'height', 'uID',
                                      sw, area)
     assert weighted[38] == 18.301521351817303
示例#2
0
 def test_mean_character(self):
     self.df_tessellation['mesh'] = mm.mean_character(self.df_tessellation)
     spatial_weights = Queen_higher(k=3, geodataframe=self.df_tessellation)
     self.df_tessellation['mesh_sw'] = mm.mean_character(
         self.df_tessellation, spatial_weights)
     self.df_tessellation[
         'area'] = area = self.df_tessellation.geometry.area
     self.df_tessellation['mesh_ar'] = mm.mean_character(
         self.df_tessellation, spatial_weights, values='area')
     self.df_tessellation['mesh_array'] = mm.mean_character(
         self.df_tessellation, spatial_weights, values=area)
     self.df_tessellation['mesh_id'] = mm.mean_character(
         self.df_tessellation, spatial_weights, values='area', rng=(10, 90))
     self.df_tessellation['mesh_iq'] = mm.mean_character(
         self.df_tessellation, spatial_weights, values='area', rng=(25, 75))
     neighbours = spatial_weights.neighbors[38]
     total_area = sum(self.df_tessellation.iloc[neighbours].geometry.area
                      ) + self.df_tessellation.geometry.area[38]
     check = total_area / (len(neighbours) + 1)
     assert self.df_tessellation['mesh'][38] == check
     assert self.df_tessellation['mesh_sw'][38] == check
     assert self.df_tessellation['mesh_ar'][38] == check
     assert self.df_tessellation['mesh_array'][38] == check
     assert self.df_tessellation['mesh_id'][38] == 2206.611646069303
     assert self.df_tessellation['mesh_iq'][38] == 2118.609142733066
     gdf = self.df_tessellation
     gdf.index = gdf.index + 20
     with pytest.raises(ValueError):
         self.df_tessellation['meshE'] = mm.mean_character(gdf)
示例#3
0
 def test_theil(self):
     full = mm.theil(self.df_tessellation, 'area', order=3)
     assert full[0] == 0.2574468431886534
     sw = Queen_higher(k=3, geodataframe=self.df_tessellation)
     full_sw = mm.theil(self.df_tessellation, 'area', spatial_weights=sw)
     assert full_sw[0] == 0.2574468431886534
     limit = mm.theil(self.df_tessellation,
                      'area',
                      spatial_weights=sw,
                      rng=(10, 90))
     assert limit[0] == 0.13302952097969373
示例#4
0
 def test_simpson(self):
     ht = mm.simpson(self.df_tessellation, 'area', order=3)
     assert ht[0] == 0.385
     sw = Queen_higher(k=3, geodataframe=self.df_tessellation)
     ht_sw = mm.simpson(self.df_tessellation, 'area', spatial_weights=sw)
     assert ht_sw[0] == 0.385
     quan_sw = mm.simpson(self.df_tessellation,
                          'area',
                          spatial_weights=sw,
                          binning='quantiles',
                          k=3)
     assert quan_sw[0] == 0.395
示例#5
0
 def test_rng(self):
     full = mm.rng(self.df_tessellation, 'area', order=3)
     assert full[0] == 8255.372874447059
     sw = Queen_higher(k=3, geodataframe=self.df_tessellation)
     full_sw = mm.rng(self.df_tessellation, 'area', spatial_weights=sw)
     assert full_sw[0] == 8255.372874447059
     area = self.df_tessellation['area']
     full2 = mm.rng(self.df_tessellation, area, order=3)
     assert full2[0] == 8255.372874447059
     limit = mm.rng(self.df_tessellation,
                    'area',
                    spatial_weights=sw,
                    rng=(10, 90))
     assert limit[0] == 4122.139212736442
示例#6
0
 def test_wall(self):
     sw = Queen_higher(geodataframe=self.df_buildings, k=1)
     wall = mm.wall(self.df_buildings)
     wall_sw = mm.wall(self.df_buildings, sw)
     assert wall[0] == wall_sw[0]
     assert wall[0] == 137.2106961418436
示例#7
0
 def test_covered_area(self):
     sw = Queen_higher(geodataframe=self.df_tessellation, k=1)
     covered = mm.covered_area(self.df_tessellation)
     covered_sw = mm.covered_area(self.df_tessellation, sw)
     assert covered[0] == covered_sw[0]
     assert covered[0] == 24115.66721833942
示例#8
0
 def test_weighted_character_sw(self):
     sw = Queen_higher(k=3, geodataframe=self.df_tessellation)
     weighted = mm.weighted_character(self.df_buildings,
                                      self.df_tessellation, 'height', 'uID',
                                      sw)
     assert weighted[38] == 18.301521351817303
示例#9
0
def simpson(objects, values, spatial_weights=None, order=None, binning='HeadTail_Breaks', **classification_kwds):
    """
    Calculates the Simpson\'s diversity index of values within k steps of morphological tessellation

    Uses `mapclassify.classifiers` under the hood for binning.

    .. math::


    Parameters
    ----------
    objects : GeoDataFrame
        GeoDataFrame containing morphological tessellation
    values : str, list, np.array, pd.Series
        the name of the dataframe column, np.array, or pd.Series where is stored character value.
    spatial_weights : libpysal.weights, optional
        spatial weights matrix - If None, Queen contiguity matrix of set order will be calculated
        based on objects.
    order : int
        order of Queen contiguity
    binning : str
        One of mapclassify classification schemes
        Options are Box_Plot, Equal_Interval, Fisher_Jenks,
        Fisher_Jenks_Sampled, HeadTail_Breaks, Jenks_Caspall,
        Jenks_Caspall_Forced, Jenks_Caspall_Sampled, Max_P_Classifier,
        Maximum_Breaks, Natural_Breaks, Quantiles, Percentiles, Std_Mean,
        User_Defined
    **classification_kwds : dict
        Keyword arguments for classification scheme
        For details see mapclassify documentation:
        https://mapclassify.readthedocs.io/en/latest/api.html

    Returns
    -------
    Series
        Series containing resulting values.

    References
    ----------

    Examples
    --------

    """
    def simpson_di(data):

        """ Given a hash { 'species': count } , returns the Simpson Diversity Index

        >>> simpson_di({'a': 10, 'b': 20, 'c': 30,})
        0.3888888888888889

        https://gist.github.com/martinjc/f227b447791df8c90568
        """

        def p(n, N):
            """ Relative abundance """
            if n == 0:
                return 0
            else:
                return float(n) / N

        N = sum(data.values())

        return sum(p(n, N)**2 for n in data.values() if n != 0)

    try:
        import mapclassify.classifiers
    except ImportError:
        raise ImportError(
            "The 'mapclassify' package is required to use the 'scheme' "
            "keyword")

    schemes = {}
    for classifier in mapclassify.classifiers.CLASSIFIERS:
        schemes[classifier.lower()] = getattr(mapclassify.classifiers,
                                              classifier)
    binning = binning.lower()
    if binning not in schemes:
        raise ValueError("Invalid binning. Binning must be in the"
                         " set: %r" % schemes.keys())

    # define empty list for results
    results_list = []

    print('Calculating Simpson\'s diversity index...')

    if spatial_weights is None:
        print('Generating weights matrix (Queen) of {} topological steps...'.format(order))
        from momepy import Queen_higher
        # matrix to define area of analysis (more steps)
        spatial_weights = Queen_higher(k=order, geodataframe=objects)
    else:
        if not all(objects.index == range(len(objects))):
            raise ValueError('Index is not consecutive range 0:x, spatial weights will not match objects.')

    if values is not None:
        if not isinstance(values, str):
            objects['mm_v'] = values
            values = 'mm_v'

    bins = schemes[binning](objects[values], **classification_kwds).bins

    for index, row in tqdm(objects.iterrows(), total=objects.shape[0]):
        neighbours = spatial_weights.neighbors[index]
        values_list = objects.iloc[neighbours][values].tolist()
        if values_list:
            values_list.append(row[values])
        else:
            values_list = [row[values]]

        sample_bins = mapclassify.classifiers.User_Defined(values_list, bins)
        counts = dict(zip(bins, sample_bins.counts))
        results_list.append(simpson_di(counts))

    series = pd.Series(results_list)

    if 'mm_v' in objects.columns:
        objects.drop(columns=['mm_v'], inplace=True)

    print('Simpson\'s diversity index calculated.')
    return series
示例#10
0
def theil(objects, values, spatial_weights=None, order=None, rng=(0, 100)):
    """
    Calculates the Theil measure of inequality of values within k steps of morphological tessellation

    Uses `pysal.explore.inequality.theil.Theil` under the hood.

    .. math::


    Parameters
    ----------
    objects : GeoDataFrame
        GeoDataFrame containing morphological tessellation
    values : str, list, np.array, pd.Series
        the name of the dataframe column, np.array, or pd.Series where is stored character value.
    spatial_weights : libpysal.weights, optional
        spatial weights matrix - If None, Queen contiguity matrix of set order will be calculated
        based on objects.
    order : int
        order of Queen contiguity
    rng : Two-element sequence containing floats in range of [0,100], optional
        Percentiles over which to compute the range. Each must be
        between 0 and 100, inclusive. The order of the elements is not important.

    Returns
    -------
    Series
        Series containing resulting values.

    References
    ----------

    Examples
    --------

    """
    from pysal.explore.inequality.theil import Theil
    # define empty list for results
    results_list = []

    print('Calculating Theil index...')

    if spatial_weights is None:
        print('Generating weights matrix (Queen) of {} topological steps...'.format(order))
        from momepy import Queen_higher
        # matrix to define area of analysis (more steps)
        spatial_weights = Queen_higher(k=order, geodataframe=objects)
    else:
        if not all(objects.index == range(len(objects))):
            raise ValueError('Index is not consecutive range 0:x, spatial weights will not match objects.')

    if values is not None:
        if not isinstance(values, str):
            objects['mm_v'] = values
            values = 'mm_v'

    for index, row in tqdm(objects.iterrows(), total=objects.shape[0]):
        neighbours = spatial_weights.neighbors[index]
        values_list = objects.iloc[neighbours][values].tolist()
        if values_list:
            values_list.append(row[values])
        else:
            values_list = [row[values]]

        if rng:
            from momepy import limit_range
            values_list = limit_range(values_list, rng=rng)
        results_list.append(Theil(values_list).T)

    series = pd.Series(results_list)

    if 'mm_v' in objects.columns:
        objects.drop(columns=['mm_v'], inplace=True)

    print('Theil index calculated.')
    return series
示例#11
0
def node_density(objects,
                 nodes,
                 spatial_weights=None,
                 order=9,
                 node_id='nodeID'):
    """
    Calculate the density of nodes within topological steps of morphological tessellation.
    Node is marked as reached if reached cell con

    .. math::


    Parameters
    ----------
    objects : GeoDataFrame
        GeoDataFrame containing tessellation objects to analyse
    nodes : GeoDataFrame
        GeoDataFrame containing nodes
    spatial_weights : libpysal.weights, optional
        spatial weights matrix - If None, Queen contiguity matrix of selected order will be calculated
        based on objects.
    order : int
        order of Queen contiguity
    node_id : str
        name of the column of objects gdf with node id.

    Returns
    -------
    Series
        Series containing resulting values.

    References
    ---------
    Jacob

    Notes
    -----

    """
    # define empty list for results
    results_list = []

    print('Calculating gross density...')

    if not all(objects.index == range(len(objects))):
        raise ValueError(
            'Index is not consecutive range 0:x, spatial weights will not match objects.'
        )

    if spatial_weights is None:
        print('Generating weights matrix (Queen) of {} topological steps...'.
              format(order))
        from momepy import Queen_higher
        # matrix to define area of analysis (more steps)
        spatial_weights = Queen_higher(k=order, geodataframe=objects)

    # iterating over rows one by one
    for index, row in tqdm(objects.iterrows(), total=objects.shape[0]):
        neighbours = spatial_weights.neighbors[index]
        neighbours.append(index)
        sub = objects.iloc[neighbours]
        subnodes = set(sub['nodeID'])
        results_list.append((len(subnodes) / sum(sub.geometry.area)) * 10000)

    series = pd.Series(results_list)
    print('Gross density calculated.')
    return series
示例#12
0
def blocks_count(tessellation, block_id, spatial_weights=None, order=5):
    """
    Calculates the weighted number of blocks

    Number of blocks within `k` topological steps defined in spatial_weights weighted by the analysed area.

    .. math::
        \\frac{\\sum_{i=1}^{n} {blocks}}{\\sum_{i=1}^{n} area_{i}}
        NOT SURE

    Parameters
    ----------
    tessellation : GeoDataFrame
        GeoDataFrame containing morphological tessellation
    block_id : str, list, np.array, pd.Series (default None)
        the name of the objects dataframe column, np.array, or pd.Series where is stored block ID.
    spatial_weights : libpysal.weights (default None)
        spatial weights matrix - If None, Queen contiguity matrix of set order will be calculated
        based on objects.
    order : int (default 5)
        order of Queen contiguity. Used only when spatial_weights=None.


    Returns
    -------
    Series
        Series containing resulting values.

    References
    ----------
    Jacob

    Examples
    --------

    Notes
    -----
    Blocks count or blocks density?

    """
    # define empty list for results
    results_list = []

    if not isinstance(block_id, str):
        block_id['mm_bid'] = block_id
        block_id = 'mm_bid'

    if not all(tessellation.index == range(len(tessellation))):
        raise ValueError(
            'Index is not consecutive range 0:x, spatial weights will not match objects.'
        )

    if spatial_weights is None:
        print('Generating weights matrix (Queen) of {} topological steps...'.
              format(order))
        from momepy import Queen_higher
        # matrix to define area of analysis (more steps)
        spatial_weights = Queen_higher(k=order, geodataframe=tessellation)

    print('Calculating blocks...')

    for index, row in tqdm(tessellation.iterrows(),
                           total=tessellation.shape[0]):
        neighbours = spatial_weights.neighbors[index]
        neighbours.append(index)
        vicinity = tessellation.iloc[neighbours]

        results_list.append(
            len(set(list(vicinity[block_id]))) / sum(vicinity.geometry.area))

    series = pd.Series(results_list)

    if 'mm_bid' in tessellation.columns:
        tessellation.drop(columns=['mm_bid'], inplace=True)

    print('Blocks calculated.')
    return series
示例#13
0
def gross_density(objects,
                  buildings,
                  area,
                  character,
                  spatial_weights=None,
                  order=3,
                  unique_id='uID'):
    """
    Calculate the density

    .. math::


    Parameters
    ----------
    objects : GeoDataFrame
        GeoDataFrame containing tessellation objects to analyse
    buildings : GeoDataFrame
        GeoDataFrame containing buildings
    area : str
        name of the column with area values
    character : str
        name of the column with values of target character for density calculation
    spatial_weights : libpysal.weights, optional
        spatial weights matrix - If None, Queen contiguity matrix of selected order will be calculated
        based on objects.
    order : int
        order of Queen contiguity
    unique_id : str
        name of the column with unique id. If there is none, it could be generated by unique_id()

    Returns
    -------
    Series
        Series containing resulting values.

    References
    ---------
    Jacob??

    Notes
    -----
    Rework, it is a mess. Terminology!
    """
    # define empty list for results
    results_list = []

    print('Calculating gross density...')

    if not all(objects.index == range(len(objects))):
        raise ValueError(
            'Index is not consecutive range 0:x, spatial weights will not match objects.'
        )

    if spatial_weights is None:
        print('Generating weights matrix (Queen) of {} topological steps...'.
              format(order))
        from momepy import Queen_higher
        # matrix to define area of analysis (more steps)
        spatial_weights = Queen_higher(k=order, geodataframe=objects)

    # iterating over rows one by one
    for index, row in tqdm(objects.iterrows(), total=objects.shape[0]):
        neighbours_id = spatial_weights.neighbors[index]
        neighbours_id.append(index)
        neighbours = objects.iloc[neighbours_id]

        fa = buildings.loc[buildings[unique_id].isin(
            neighbours[unique_id])][character]
        results_list.append(sum(fa) / sum(neighbours[area]))

    series = pd.Series(results_list)
    print('Gross density calculated.')
    return series
示例#14
0
def building_adjacency(objects,
                       tessellation,
                       spatial_weights=None,
                       spatial_weights_higher=None,
                       order=3,
                       unique_id='uID'):
    """
    Calculate the level of building adjacency

    Building adjacency reflects how much buildings tend to join together into larger structures.
    It is calculated as a ratio of joined built-up structures and buildings within k topological steps.

    .. math::


    Parameters
    ----------
    objects : GeoDataFrame
        GeoDataFrame containing objects to analyse
    tessellation : GeoDataFrame
        GeoDataFrame containing morphological tessellation - source of spatial_weights and spatial_weights_higher.
        It is crucial to use exactly same input as was used durign the calculation of weights matrix and spatial_weights_higher.
        If spatial_weights or spatial_weights_higher is None, tessellation is used to calulate it.
    spatial_weights : libpysal.weights, optional
        spatial weights matrix - If None, Queen contiguity matrix will be calculated
        based on tessellation
    spatial_weights_higher : libpysal.weights, optional
        spatial weights matrix - If None, Queen contiguity of higher order will be calculated
        based on tessellation
    order : int
        Order of Queen contiguity

    Returns
    -------
    Series
        Series containing resulting values.

    References
    ---------
    Vanderhaegen S and Canters F (2017) Mapping urban form and function at city
    block level using spatial metrics. Landscape and Urban Planning 167: 399–409.

    Examples
    --------
    >>> buildings_df['adjacency'] = momepy.building_adjacency(buildings_df, tessellation_df, unique_id='uID')
    Calculating adjacency...
    Calculating spatial weights...
    Spatial weights ready...
    Generating weights matrix (Queen) of 3 topological steps...
    Generating dictionary of built-up patches...
    100%|██████████| 144/144 [00:00<00:00, 9301.73it/s]
    Calculating adjacency within k steps...
    100%|██████████| 144/144 [00:00<00:00, 335.55it/s]
    Adjacency calculated.
    >>> buildings_df['adjacency'][10]
    0.23809523809523808
    """
    # define empty list for results
    results_list = []

    print('Calculating adjacency...')

    if not all(tessellation.index == range(len(tessellation))):
        raise ValueError(
            'Index is not consecutive range 0:x, spatial weights will not match objects.'
        )

    # if weights matrix is not passed, generate it from objects
    if spatial_weights is None:
        print('Calculating spatial weights...')
        from libpysal.weights import Queen
        spatial_weights = Queen.from_dataframe(objects, silence_warnings=True)
        print('Spatial weights ready...')

    if spatial_weights_higher is None:
        print('Generating weights matrix (Queen) of {} topological steps...'.
              format(order))
        from momepy import Queen_higher
        # matrix to define area of analysis (more steps)
        spatial_weights_higher = Queen_higher(k=order,
                                              geodataframe=tessellation)

    print('Generating dictionary of built-up patches...')
    # dict to store nr of courtyards for each uID
    patches = {}
    jID = 1
    for index, row in tqdm(objects.iterrows(), total=objects.shape[0]):

        # if the id is already present in courtyards, continue (avoid repetition)
        if index in patches:
            continue
        else:
            to_join = [index
                       ]  # list of indices which should be joined together
            neighbours = []  # list of neighbours
            weights = spatial_weights.neighbors[
                index]  # neighbours from spatial weights
            for w in weights:
                neighbours.append(w)  # make a list from weigths

            for n in neighbours:
                while n not in to_join:  # until there is some neighbour which is not in to_join
                    to_join.append(n)
                    weights = spatial_weights.neighbors[n]
                    for w in weights:
                        neighbours.append(
                            w
                        )  # extend neighbours by neighbours of neighbours :)
            for b in to_join:
                patches[b] = jID  # fill dict with values
            jID = jID + 1

    print('Calculating adjacency within k steps...')
    for index, row in tqdm(objects.iterrows(), total=objects.shape[0]):
        uid = tessellation.loc[tessellation[unique_id] ==
                               row[unique_id]].index[0]
        neighbours = spatial_weights_higher.neighbors[uid]

        neighbours_ids = tessellation.iloc[neighbours][unique_id]
        neighbours_ids = neighbours_ids.append(
            pd.Series(row[unique_id], index=[index]))
        building_neighbours = objects.loc[objects[unique_id].isin(
            neighbours_ids)]
        indices = list(building_neighbours.index)
        patches_sub = [patches[x] for x in indices]
        patches_nr = len(set(patches_sub))

        results_list.append(patches_nr / len(building_neighbours))

    series = pd.Series(results_list)

    print('Adjacency calculated.')
    return series
示例#15
0
def mean_interbuilding_distance(objects,
                                tessellation,
                                unique_id,
                                spatial_weights=None,
                                spatial_weights_higher=None,
                                order=3):
    """
    Calculate the mean interbuilding distance within x topological steps

    Interbuilding distances are calculated between buildings on adjacent cells based on `spatial_weights`.

    .. math::


    Parameters
    ----------
    objects : GeoDataFrame
        GeoDataFrame containing objects to analyse
    tessellation : GeoDataFrame
        GeoDataFrame containing morphological tessellation - source of spatial_weights and spatial_weights_higher.
        It is crucial to use exactly same input as was used durign the calculation of weights matrix and spatial_weights_higher.
        If spatial_weights or spatial_weights_higher is None, tessellation is used to calulate it.
    unique_id : str
        name of the column with unique id
    spatial_weights : libpysal.weights, optional
        spatial weights matrix - If None, Queen contiguity matrix will be calculated
        based on tessellation
    spatial_weights_higher : libpysal.weights, optional
        spatial weights matrix - If None, Queen contiguity of higher order will be calculated
        based on tessellation
    order : int
        Order of Queen contiguity

    Returns
    -------
    Series
        Series containing resulting values.

    References
    ---------
    ADD, but it is adapted quite a lot.

    Notes
    -----
    Fix terminology, it is unclear.
    Fix UserWarning.

    Examples
    --------
    >>> buildings_df['mean_interbuilding_distance'] = momepy.mean_interbuilding_distance(buildings_df, tessellation_df, 'uID')
    Calculating mean interbuilding distances...
    Generating weights matrix (Queen)...
    Generating weights matrix (Queen) of 3 topological steps...
    Generating adjacency matrix based on weights matrix...
    Computing interbuilding distances...
    100%|██████████| 746/746 [00:03<00:00, 200.14it/s]
    Computing mean interbuilding distances...
    100%|██████████| 144/144 [00:00<00:00, 317.42it/s]
    Mean interbuilding distances calculated.
    >>> buildings_df['mean_interbuilding_distance'][0]
    29.305457092042744
    """
    if not all(tessellation.index == range(len(tessellation))):
        raise ValueError(
            'Index is not consecutive range 0:x, spatial weights will not match objects.'
        )

    print('Calculating mean interbuilding distances...')
    if spatial_weights is None:
        print('Generating weights matrix (Queen)...')
        from libpysal.weights import Queen
        # matrix to capture interbuilding relationship
        spatial_weights = Queen.from_dataframe(tessellation)

    if spatial_weights_higher is None:
        print('Generating weights matrix (Queen) of {} topological steps...'.
              format(order))
        from momepy import Queen_higher
        # matrix to define area of analysis (more steps)
        spatial_weights_higher = Queen_higher(k=order,
                                              geodataframe=tessellation)

    # define empty list for results
    results_list = []

    print('Generating adjacency matrix based on weights matrix...')
    # define adjacency list from lipysal
    adj_list = spatial_weights.to_adjlist()
    adj_list['distance'] = -1

    print('Computing interbuilding distances...')
    # measure each interbuilding distance of neighbours and save them to adjacency list
    for index, row in tqdm(adj_list.iterrows(), total=adj_list.shape[0]):
        inverted = adj_list[(adj_list.focal == row.neighbor)][(
            adj_list.neighbor == row.focal)].iloc[0]['distance']
        if inverted == -1:
            object_id = tessellation.iloc[row.focal.astype(int)][unique_id]
            building_object = objects.loc[objects[unique_id] == object_id]

            neighbours_id = tessellation.iloc[row.neighbor.astype(
                int)][unique_id]
            building_neighbour = objects.loc[objects[unique_id] ==
                                             neighbours_id]
            adj_list.loc[
                index,
                'distance'] = building_neighbour.iloc[0].geometry.distance(
                    building_object.iloc[0].geometry)
        else:
            adj_list.at[index, 'distance'] = inverted

    print('Computing mean interbuilding distances...')
    # iterate over objects to get the final values
    for index, row in tqdm(objects.iterrows(), total=objects.shape[0]):
        # id to match spatial weights
        uid = tessellation.loc[tessellation[unique_id] ==
                               row[unique_id]].index[0]
        # define neighbours based on weights matrix defining analysis area
        neighbours = spatial_weights_higher.neighbors[uid]
        neighbours.append(uid)
        if neighbours:
            selection = adj_list[adj_list.focal.isin(neighbours)][
                adj_list.neighbor.isin(neighbours)]
            results_list.append(np.nanmean(selection.distance))

    series = pd.Series(results_list)
    print('Mean interbuilding distances calculated.')
    return series