Repository: martinfleis/momepy
Pull request #14 (2019-06-04 14:04:04)

ENH: network-based algorithms

codecov[bot]: # [Codecov](https://codecov.io/gh/martinfleis/momepy/pull/14?src=pr&el=h1) Report
> Merging [#14](https://codecov.io/gh/martinfleis/momepy/pull/14?src=pr&el=desc) into [master](https://codecov.io/gh/martinfleis/momepy/commit/845e19ded41b12afffac170ddfb8b6aace042948?src=pr&el=desc) will **decrease** coverage by `0.69%`.
> The diff coverage is `25%`.

[![Impacted file tree graph](https://codecov.io/gh/martinfleis/momepy/pull/14/graphs/tree.svg?width=650&token=i04999UxkK&height=150&src=pr)](https://codecov.io/gh/martinfleis/momepy/pull/14?src=pr&el=tree)

```diff
@@            Coverage Diff            @@
##           master      #14     +/-   ##
=========================================
- Coverage   88.08%   87.38%   -0.7%     
=========================================
  Files           9        9             
  Lines        2124     2149     +25     
=========================================
+ Hits         1871     1878      +7     
- Misses        253      271     +18
```


| [Impacted Files](https://codecov.io/gh/martinfleis/momepy/pull/14?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [momepy/utils.py](https://codecov.io/gh/martinfleis/momepy/pull/14/diff?src=pr&el=tree#diff-bW9tZXB5L3V0aWxzLnB5) | `87.97% <100%> (+0.07%)` | :arrow_up: |
| [momepy/connectivity.py](https://codecov.io/gh/martinfleis/momepy/pull/14/diff?src=pr&el=tree#diff-bW9tZXB5L2Nvbm5lY3Rpdml0eS5weQ==) | `26.82% <23.52%> (+26.82%)` | :arrow_up: |
| [momepy/intensity.py](https://codecov.io/gh/martinfleis/momepy/pull/14/diff?src=pr&el=tree#diff-bW9tZXB5L2ludGVuc2l0eS5weQ==) | `76.71% <31.57%> (-16.4%)` | :arrow_down: |
| [momepy/dimension.py](https://codecov.io/gh/martinfleis/momepy/pull/14/diff?src=pr&el=tree#diff-bW9tZXB5L2RpbWVuc2lvbi5weQ==) | `92.42% <5.55%> (-5.23%)` | :arrow_down: |
| [momepy/distribution.py](https://codecov.io/gh/martinfleis/momepy/pull/14/diff?src=pr&el=tree#diff-bW9tZXB5L2Rpc3RyaWJ1dGlvbi5weQ==) | `90.69% <0%> (-3.62%)` | :arrow_down: |
| [momepy/elements.py](https://codecov.io/gh/martinfleis/momepy/pull/14/diff?src=pr&el=tree#diff-bW9tZXB5L2VsZW1lbnRzLnB5) | `83.08% <0%> (+0.73%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/martinfleis/momepy/pull/14?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/martinfleis/momepy/pull/14?src=pr&el=footer). Last update [845e19d...3abac18](https://codecov.io/gh/martinfleis/momepy/pull/14?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/docs/api.rst b/docs/api.rst
index 9a513d7..c895a9a 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -16,6 +16,7 @@ elements
 
    blocks
    get_network_id
+   get_node_id
    tessellation
 
 dimension
@@ -31,6 +32,7 @@ dimension
    longest_axis_length
    mean_character
    perimeter
+   segments_length
    street_profile
    volume
    wall
@@ -83,9 +85,20 @@ intensity
    blocks_count
    courtyards
    covered_area_ratio
-   elements_in_block
+   elements_count
    floor_area_ratio
    gross_density
+   reached
+
+connectivity
+------------
+.. autosummary::
+   :toctree: generated/
+
+   cds_length
+   mean_node_dist
+   meshedness
+   node_degree
 
 diversity
 ---------
diff --git a/momepy/__init__.py b/momepy/__init__.py
index 50852b9..f963349 100644
--- a/momepy/__init__.py
+++ b/momepy/__init__.py
@@ -4,5 +4,6 @@ from .utils import *
 from .elements import *
 from .diversity import *
 from .distribution import *
+from .connectivity import *
 from .shape import *
 import momepy.datasets
diff --git a/momepy/connectivity.py b/momepy/connectivity.py
index 0c4f666..8307bed 100644
--- a/momepy/connectivity.py
+++ b/momepy/connectivity.py
@@ -5,87 +5,144 @@
 # definitons of connectivity characters
 import networkx as nx
 from tqdm import tqdm
+import numpy as np
 
-from .utils import gdf_to_nx, nx_to_gdf
-
-
-# edge-based example
-def betweenness(graph=None, geodataframe=None, target='series', unique_id=None):
-    if graph is not None and geodataframe is not None:
-        import warnings
-        warnings.war('Both graph and geodataframe were passed. Graph will be used for calculation.')
-
-    netx = None
-    if graph is not None:
-        netx = graph
-    elif geodataframe is not None:
-        if netx is None:
-            netx = gdf_to_nx(geodataframe)
-    else:
-        raise Warning('Either graph or geodataframe must be passed as an argument.')
-
-    betweennesss = nx.edge_betweenness_centrality(netx)  # calculate
-    nx.set_edge_attributes(netx, betweennesss, 'mm_between')
-
-    if target == 'series':
-        edges = nx_to_gdf(netx, nodes=False)
-        joined = geodataframe.join(edges[[unique_id, 'mm_between']], rsuffix='r')
-        series = joined['mm_between']
-        return series
-    elif target == 'graph':
-        return netx
-    raise Warning('Target {} is not supported. Use "series" for pandas.Series or "graph" for networkx.Graph.'.format(target))
-
-
-# node based example
-def node_degree(graph=None, geodataframe=None, target='gdf'):
-    if graph is not None and geodataframe is not None:
-        import warnings
-        warnings.war('Both graph and geodataframe were passed. Graph will be used for calculation.')
-
-    netx = None
-    if graph is not None:
-        netx = graph
-    elif geodataframe is not None:
-        if netx is None:
-            netx = gdf_to_nx(geodataframe)
-    else:
-        raise Warning('Either graph or geodataframe must be passed as an argument.')
-
-    degree = dict(nx.degree(netx))
-    nx.set_node_attributes(netx, degree, 'degree')
-
-    if target == 'gdf':
-        nodes = nx_to_gdf(netx, edges=False)
-        return nodes
-    elif target == 'graph':
-        return netx
-    raise Warning('Target {} is not supported. Use "gdf" for geopandas.GeoDataFrame or "graph" for networkx.Graph.'.format(target))
-
-
-def meshedness(graph=None, geodataframe=None, radius=5, target='gdf'):
-    if graph is not None and geodataframe is not None:
-        import warnings
-        warnings.war('Both graph and geodataframe were passed. Graph will be used for calculation.')
-
-    netx = None
-    if graph is not None:
-        netx = graph
-    elif geodataframe is not None:
-        if netx is None:
-            netx = gdf_to_nx(geodataframe)
-    else:
-        raise Warning('Either graph or geodataframe must be passed as an argument.')
+
+def meshedness(graph, radius=5, name='meshedness'):
+    """
+    Calculates meshedness for subgraph around each node.
+
+    Subgraph is generated around each node within set topological radius.
+
+    .. math::
+
+
+    Parameters
+    ----------
+    graph : networkx.Graph
+        Graph representing street network.
+        Ideally genereated from GeoDataFrame using :py:func:`momepy.gdf_to_nx`
+    radius: int
+        number of topological steps defining the extent of subgraph
+    name : str, optional
+        calculated attribute name
+
+    Returns
+    -------
+    Graph
+        networkx.Graph
+
+    References
+    ----------
+
+    Examples
+    --------
+
+    """
+    netx = graph
 
     for n in tqdm(netx, total=len(netx)):
         sub = nx.ego_graph(netx, n, radius=radius, undirected=True)  # define subgraph of steps=radius
         e = sub.number_of_edges()
         v = sub.number_of_nodes()
-        netx.nodes[n]['meshedness'] = (e - v + 1) / (2 * v - 5)  # save value calulated for subgraph to node
-
-    if target == 'gdf':
-        nodes = nx_to_gdf(netx, edges=False)
-        return nodes
-    elif target == 'graph':
-        return netx
-    raise Warning('Target {} is not supported. Use "gdf" for geopandas.GeoDataFrame or "graph" for networkx.Graph.'.format(target))
+        netx.nodes[n][name] = (e - v + 1) / (2 * v - 5)  # save value calulated for subgraph to node
+
+    return netx
+
+
+def mean_node_dist(graph, name='meanlen', length='mm_len'):
+    """
+    Calculates mean distance to neighbouring nodes.
+
+    .. math::
+
+
+    Parameters
+    ----------
+    graph : networkx.Graph
+        Graph representing street network.
+        Ideally genereated from GeoDataFrame using :py:func:`momepy.gdf_to_nx`
+    name : str, optional
+        calculated attribute name
+    length : str, optional
+        name of attribute of segment length (geographical)
+
+    Returns
+    -------
+    Graph
+        networkx.Graph
+
+    References
+    ----------
+
+    Examples
+    --------
+
+    """
+    netx = graph
+
+    for n, nbrs in tqdm(netx.adj.items(), total=len(netx)):
+        lengths = []
+        for nbr, eattr in nbrs.items():
+            lengths.append(eattr[length])
+        netx.nodes[n][name] = np.mean(lengths)
+
+    return netx
+
+
+def cds_length(graph=None, radius=5, mode='sum', name='cds_len', degree='degree', length='mm_len'):
+    """
+    Calculates length of cul-de-sacs for subgraph around each node.
+
+    Subgraph is generated around each node within set topological radius.
+
+    .. math::
+
+
+    Parameters
+    ----------
+    graph : networkx.Graph
+        Graph representing street network.
+        Ideally genereated from GeoDataFrame using :py:func:`momepy.gdf_to_nx`
+    radius : int
+        number of topological steps defining the extent of subgraph
+    name : str, optional
+        calculated attribute name
+    degree : str
+        name of attribute of node degree (:py:func:`momepy.node_degree`)
+    length : str, optional
+        name of attribute of segment length (geographical)
+
+    Returns
+    -------
+    Graph
+        networkx.Graph
+
+    References
+    ----------
+
+    Examples
+    --------
+
+    """
+    # node degree needed beforehand
+    netx = graph
+
+    for u, v in netx.edges():
+        if netx.nodes[u][degree] == 1 or netx.nodes[v][degree] == 1:
+            netx[u][v]['cdsbool'] = True
+        else:
+            netx[u][v]['cdsbool'] = False
+
+    for n in tqdm(netx, total=len(netx)):
+        sub = nx.ego_graph(netx, n, radius=radius, undirected=True)  # define subgraph of steps=radius
+        lens = []
+        for u, v, cds in sub.edges.data('cdsbool'):
+            if cds:
+                lens.append(sub[u][v][length])
+        if mode == 'sum':
+            netx.nodes[n][name] = sum(lens)  # save value calulated for subgraph to node
+        elif mode == 'mean':
+            netx.nodes[n][name] = np.mean(lens)  # save value calulated for subgraph to node
+
+    return netx
diff --git a/momepy/dimension.py b/momepy/dimension.py
index 2d3ef41..3a9ed03 100644
--- a/momepy/dimension.py
+++ b/momepy/dimension.py
@@ -818,3 +818,50 @@ def wall(objects, spatial_weights=None):
     series = pd.Series(results_list)
     print('Perimeter wall length calculated.')
     return series
+
+
+def segments_length(streets, spatial_weights=None, mean=False):
+    """
+    Calculate the cummulative or mean length of segments.
+
+    Length of segments within set topological distance from each of them.
+    Reached topological distance should be captured by spatial_weights. If mean=False
+    it will return total length, if mean=True it will return mean value.
+
+    Parameters
+    ----------
+    streets : GeoDataFrame
+        GeoDataFrame containing streets (segments) to analyse
+    spatial_weights : libpysal.weights, optional
+        spatial weights matrix - If None, Queen contiguity matrix will be calculated
+        based on streets.
+    mean : boolean, optional
+        If mean=False it will return total length, if mean=True it will return mean value
+
+    Returns
+    -------
+    Series
+        Series containing resulting values.
+    """
+    results_list = []
+
+    print('Calculating segments length...')
+
+    if spatial_weights is None:
+        print('Calculating spatial weights...')
+        from libpysal.weights import Queen
+        spatial_weights = Queen.from_dataframe(streets)
+        print('Spatial weights ready...')
+
+    for index, row in tqdm(streets.iterrows(), total=streets.shape[0]):
+        neighbours = spatial_weights.neighbors[index]
+        neighbours.append(index)
+        dims = streets.iloc[neighbours].geometry.length
+        if mean:
+            results_list.append(np.mean(dims))
+        else:
+            results_list.append(sum(dims))
+
+    series = pd.Series(results_list)
+    print('Segments length calculated.')
+    return series
diff --git a/momepy/distribution.py b/momepy/distribution.py
index a84d61c..abc9791 100644
--- a/momepy/distribution.py
+++ b/momepy/distribution.py
@@ -915,26 +915,38 @@ def neighbours(objects, spatial_weights=None, weighted=False):
     return series
 
 
-def node_degree(graph=None, geodataframe=None, target='gdf'):
-    if graph is not None and geodataframe is not None:
-        import warnings
-        warnings.war('Both graph and geodataframe were passed. Graph will be used for calculation.')
-
-    netx = None
-    if graph is not None:
-        netx = graph
-    elif geodataframe is not None:
-        if netx is None:
-            netx = gdf_to_nx(geodataframe)
-    else:
-        raise Warning('Either graph or geodataframe must be passed as an argument.')
+def node_degree(graph, name='degree'):
+    """
+    Calculates node degree for each node.
+
+    Wrapper around `networkx.degree()`
+
+    .. math::
+
+
+    Parameters
+    ----------
+    graph : networkx.Graph
+        Graph representing street network.
+        Ideally genereated from GeoDataFrame using :py:func:`momepy.gdf_to_nx`
+    name : str, optional
+        calculated attribute name
+
+    Returns
+    -------
+    Graph
+        networkx.Graph
+
+    References
+    ----------
+
+    Examples
+    --------
+
+    """
+    netx = graph
 
     degree = dict(nx.degree(netx))
-    nx.set_node_attributes(netx, degree, 'degree')
-
-    if target == 'gdf':
-        nodes = nx_to_gdf(netx, edges=False)
-        return nodes
-    elif target == 'graph':
-        return netx
-    raise Warning('Target {} is not supported. Use "gdf" for geopandas.GeoDataFrame or "graph" for networkx.Graph.'.format(target))
+    nx.set_node_attributes(netx, degree, name)
+
+    return netx
diff --git a/momepy/intensity.py b/momepy/intensity.py
index f48926c..e286322 100644
--- a/momepy/intensity.py
+++ b/momepy/intensity.py
@@ -6,6 +6,7 @@
 
 from tqdm import tqdm  # progress bar
 import pandas as pd
+import numpy as np
 import collections
 
 
@@ -111,27 +112,30 @@ def floor_area_ratio(objects, look_for, area_column, look_for_area_column, id_co
     return series
 
 
-def elements_in_block(blocks, elements, left_id, right_id, weighted=False):
+def elements_count(aggr, elements, left_id, right_id, weighted=False):
     """
-    Calculate the number of elements within block.
+    Calculate the number of elements within aggregated structure.
 
-    If weighted=True, number of elements will be divided by the area of block, to return relative value.
+    Aggregated structure can be typically block, street segment or street node. Elements has to have
+    unique id of aggregated structure assigned before hand (e.g. using :py:func:`momepy.elements.get_network_id`).
+    If weighted=True, number of elements will be divided by the area of lenght (based on geometry type) of aggregated
+    element, to return relative value.
 
     .. math::
-        \\sum_{i \\in block} (n_i);\\space \\frac{\\sum_{i \\in block} (n_i)}{area_{block}}
+        \\sum_{i \\in aggr} (n_i);\\space \\frac{\\sum_{i \\in aggr} (n_i)}{area_{aggr}}
 
     Parameters
     ----------
-    blocks : GeoDataFrame
-        GeoDataFrame containing blocks to analyse
-    buildings : GeoDataFrame
-        GeoDataFrame containing buildings to analyse
+    aggr : GeoDataFrame
+        GeoDataFrame containing aggregation to analyse
+    elements : GeoDataFrame
+        GeoDataFrame containing elements to analyse
     left_id : str
-        name of the column where is stored block ID in blocks gdf
+        name of the column where is stored unique ID in aggr
     right_id : str
-        name of the column where is stored block ID in elements gdf
+        name of the column where is stored unique ID of aggregation in elements gdf
     weighted : bool (default False)
-        if weighted=True, number of buildings will be divided by the area of block, to return relative value.
+        if weighted=True, count will be divided by the area or length
 
     Returns
     -------
@@ -147,17 +151,19 @@ def elements_in_block(blocks, elements, left_id, right_id, weighted=False):
     STUDY OF RESILIENCE IN URBAN FORM. LEARNING FROM THE CASE OF GORBALS. Glasgow.
     """
     count = collections.Counter(elements[right_id])
-
-    results_list = []
-    for index, row in tqdm(blocks.iterrows(), total=blocks.shape[0]):
-        if weighted is True:
-            results_list.append(count[row[left_id]] / row.geometry.area)
+    df = pd.DataFrame.from_dict(count, orient='index', columns=['mm_count'])
+    joined = aggr.join(df['mm_count'], on=left_id)
+    joined['mm_count'][np.isnan(joined['mm_count'])] = 0
+
+    if weighted:
+        if aggr.geometry[0].type in ['Polygon', 'MultiPolygon']:
+            joined['mm_count'] = joined['mm_count'] / aggr.geometry.area
+        elif aggr.geometry[0].type in ['LineString', 'MultiLineString']:
+            joined['mm_count'] = joined['mm_count'] / aggr.geometry.length
         else:
-            results_list.append(count[row[left_id]])
+            raise TypeError('Geometry type does not support weighting.')
 
-    series = pd.Series(results_list)
-
-    return series
+    return joined['mm_count']
 
 
 def courtyards(objects, block_id, spatial_weights=None):
@@ -374,11 +380,78 @@ def blocks_count(tessellation, block_id, spatial_weights=None, order=5):
     print('Blocks calculated.')
     return series
 
-# objects.to_file("/Users/martin/Strathcloud/Personal Folders/Test data/Prague/p7_voro_single4.shp")
-#
-# objects = gpd.read_file("/Users/martin/Strathcloud/Personal Folders/Test data/Prague/p7_voro_single.shp")
-# column_name = 'test'
-# objects
-# objects2.head
-# objects['geometry'] = objects.centroid
-# objects_centroids
+
+def reached(streets, elements, unique_id, spatial_weights=None, mode='count'):
+    """
+    Calculates the number of elements reached within topological steps
+
+    Number of elements within topological steps defined in spatial_weights. If
+    spatial_weights are None, it will assume topological distance 0 (element itself).
+    If mode='area', returns sum of areas of reached elements. Requires unique_id
+    of streets assigned beforehand (e.g. using :py:func:`momepy.elements.get_network_id`).
+
+    .. math::
+
+
+    Parameters
+    ----------
+    streets : GeoDataFrame
+        GeoDataFrame containing streets (either segments or nodes)
+    elements : GeoDataFrame
+        GeoDataFrame containing elements to be counted
+    unique_id : str, list, np.array, pd.Series (default None)
+        the name of the objects dataframe column, np.array, or pd.Series where is
+        stored ID of streets (segments or nodes).
+    spatial_weights : libpysal.weights (default None)
+        spatial weights matrix
+    mode : str (default 'count')
+        mode of calculation. If 'count' function will return the count of reached elements.
+        If 'area', it will return sum of areas of reached elements
+
+
+    Returns
+    -------
+    Series
+        Series containing resulting values.
+
+    References
+    ----------
+
+    Examples
+    --------
+
+    """
+    # define empty list for results
+    results_list = []
+
+    print('Calculating reached {}...'.format(mode))
+
+    if not isinstance(unique_id, str):
+        elements['mm_id'] = unique_id
+        unique_id = 'mm_id'
+
+    if mode == 'count':
+        count = collections.Counter(elements[unique_id])
+
+    # iterating over rows one by one
+    for index, row in tqdm(streets.iterrows(), total=streets.shape[0]):
+        if spatial_weights is None:
+            ids = [row.nID]
+        else:
+            neighbours = spatial_weights.neighbors[index]
+            neighbours.append(index)
+            ids = streets.iloc[neighbours].nID
+        if mode == 'count':
+            counts = []
+            for nid in ids:
+                counts.append(count[nid])
+            results_list.append(sum(counts))
+        elif mode == 'area':
+            results_list.append(sum(elements.loc[elements[unique_id].isin(ids)].geometry.area))
+
+    series = pd.Series(results_list)
+    if 'mm_id' in elements.columns:
+        elements.drop(columns=['mm_id'], inplace=True)
+
+    print('Reached {} calculated.'.format(mode))
+    return series
diff --git a/momepy/utils.py b/momepy/utils.py
index 3ec1dd0..80bd546 100644
--- a/momepy/utils.py
+++ b/momepy/utils.py
@@ -80,7 +80,7 @@ def Queen_higher(k, geodataframe=None, weights=None):
     return joined
 
 
-def gdf_to_nx(gdf_network):
+def gdf_to_nx(gdf_network, length='mm_len'):
     """
     Convert LineString GeoDataFrame to networkx.Graph
 
@@ -88,6 +88,8 @@ def gdf_to_nx(gdf_network):
     ----------
     gdf_network : GeoDataFrame
         GeoDataFrame containing objects to convert
+    length : str, optional
+        name of attribute of segment length (geographical) which will be saved to graph
 
     Returns
     -------
@@ -98,6 +100,7 @@ def gdf_to_nx(gdf_network):
     # generate graph from GeoDataFrame of LineStrings
     net = nx.Graph()
     net.graph['crs'] = gdf_network.crs
+    gdf_network[length] = gdf_network.geometry.length
     fields = list(gdf_network.columns)
 
     for index, row in gdf_network.iterrows():


Test patch:
diff --git a/tests/test_connectivity.py b/tests/test_connectivity.py
new file mode 100644
index 0000000..3bf6fbd
--- /dev/null
+++ b/tests/test_connectivity.py
@@ -0,0 +1,30 @@
+import momepy as mm
+import geopandas as gpd
+
+
+class TestConnectivity:
+
+    def setup_method(self):
+
+        test_file_path = mm.datasets.get_path('bubenec')
+        self.df_streets = gpd.read_file(test_file_path, layer='streets')
+        self.network = mm.gdf_to_nx(self.df_streets)
+        self.network = mm.node_degree(self.network)
+
+    def test_meshedness(self):
+        net = mm.meshedness(self.network)
+        check = 0.022222222222222223
+        assert net.nodes[(1603650.450422848, 6464368.600601688)]['meshedness'] == check
+
+    def test_mean_node_dist(self):
+        net = mm.mean_node_dist(self.network)
+        check = 267.15635764665024
+        assert net.nodes[(1603650.450422848, 6464368.600601688)]['meanlen'] == check
+
+    def test_cds_length(self):
+        net = mm.cds_length(self.network)
+        net = mm.cds_length(self.network, mode='mean', name='cds_mean')
+        sumval = 1678.873603417499
+        mean = 209.8592004271874
+        assert net.nodes[(1603650.450422848, 6464368.600601688)]['cds_len'] == sumval
+        assert net.nodes[(1603650.450422848, 6464368.600601688)]['cds_mean'] == mean
diff --git a/tests/test_dimension.py b/tests/test_dimension.py
index 47678a6..8de4f08 100644
--- a/tests/test_dimension.py
+++ b/tests/test_dimension.py
@@ -172,3 +172,9 @@ class TestDimensions:
         wall_sw = mm.wall(self.df_buildings, sw)
         assert wall[0] == wall_sw[0]
         assert wall[0] == 137.2106961418436
+
+    def test_segments_length(self):
+        absol = mm.segments_length(self.df_streets)
+        mean = mm.segments_length(self.df_streets, mean=True)
+        assert max(absol) == 2052.434384404494
+        assert max(mean) == 278.1512323800796
diff --git a/tests/test_distribution.py b/tests/test_distribution.py
index 92ed0dc..69dbc33 100644
--- a/tests/test_distribution.py
+++ b/tests/test_distribution.py
@@ -102,9 +102,5 @@ class TestDistribution:
 
     def test_node_degree(self):
         graph = mm.gdf_to_nx(self.df_streets)
-        deg1 = mm.node_degree(graph=graph, target='graph')
-        deg2 = mm.node_degree(geodataframe=self.df_streets, target='graph')
-        deg3 = mm.node_degree(graph=graph, target='gdf')
+        deg1 = mm.node_degree(graph=graph)
         assert deg1.nodes[(1603650.450422848, 6464368.600601688)]['degree'] == 4
-        assert deg2.nodes[(1603650.450422848, 6464368.600601688)]['degree'] == 4
-        assert deg3.degree.mean() == 2.0625
diff --git a/tests/test_intensity.py b/tests/test_intensity.py
index bba6017..d7eae15 100644
--- a/tests/test_intensity.py
+++ b/tests/test_intensity.py
@@ -4,7 +4,7 @@ import numpy as np
 from libpysal.weights import Queen
 
 
-class TestDistribution:
+class TestIntensity:
 
     def setup_method(self):
 
@@ -12,10 +12,12 @@ class TestDistribution:
         self.df_buildings = gpd.read_file(test_file_path, layer='buildings')
         self.df_streets = gpd.read_file(test_file_path, layer='streets')
         self.df_tessellation = gpd.read_file(test_file_path, layer='tessellation')
+        self.df_streets['nID'] = mm.unique_id(self.df_streets)
         self.df_buildings['height'] = np.linspace(10., 30., 144)
         self.df_tessellation['area'] = self.df_tessellation.geometry.area
         self.df_buildings['area'] = self.df_buildings.geometry.area
         self.df_buildings['fl_area'] = mm.floor_area(self.df_buildings, 'height')
+        self.df_buildings['nID'] = mm.get_network_id(self.df_buildings, self.df_streets, 'uID', 'nID')
         self.df_buildings, self.df_tessellation, self.blocks = mm.blocks(self.df_tessellation, self.df_streets, self.df_buildings, 'bID', 'uID')
 
     def test_covered_area_ratio(self):
@@ -28,13 +30,15 @@ class TestDistribution:
         check = 1.910949846262234
         assert far.mean() == check
 
-    def test_elements_in_block(self):
-        eib = mm.elements_in_block(self.blocks, self.df_buildings, 'bID', 'bID')
-        weib = mm.elements_in_block(self.blocks, self.df_buildings, 'bID', 'bID', weighted=True)
+    def test_elements_count(self):
+        eib = mm.elements_count(self.blocks, self.df_buildings, 'bID', 'bID')
+        weib = mm.elements_count(self.blocks, self.df_buildings, 'bID', 'bID', weighted=True)
+        weis = mm.elements_count(self.df_streets, self.df_buildings, 'nID', 'nID', weighted=True)
         check_eib = [13, 14, 8, 26, 24, 17, 23, 19]
         check_weib = 0.00040170607189453996
         assert eib.tolist() == check_eib
         assert weib.mean() == check_weib
+        assert weis.mean() == 0.02077458117886389
 
     def test_courtyards(self):
         courtyards = mm.courtyards(self.df_buildings, 'bID')
@@ -59,3 +63,12 @@ class TestDistribution:
         check = 3.142437439120778e-05
         assert count.mean() == check
         assert count2.mean() == check
+
+    def test_reached(self):
+        count = mm.reached(self.df_streets, self.df_buildings, 'nID')
+        area = mm.reached(self.df_streets, self.df_buildings, 'nID', mode='area')
+        sw = mm.Queen_higher(k=2, geodataframe=self.df_streets)
+        count_sw = mm.reached(self.df_streets, self.df_buildings, 'nID', sw)
+        assert max(count) == 18
+        assert max(area) == 18085.45897711331
+        assert max(count_sw) == 133