Repository: martinfleis/momepy
Pull request #54 (2019-08-13 09:11:57)

ENH: allow different left and right ID names

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

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

```diff
@@            Coverage Diff            @@
##           master     #54      +/-   ##
=========================================
- Coverage   91.14%   90.8%   -0.34%     
=========================================
  Files           9       9              
  Lines        2123    2143      +20     
=========================================
+ Hits         1935    1946      +11     
- Misses        188     197       +9
```


| [Impacted Files](https://codecov.io/gh/martinfleis/momepy/pull/54?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [momepy/distribution.py](https://codecov.io/gh/martinfleis/momepy/pull/54/diff?src=pr&el=tree#diff-bW9tZXB5L2Rpc3RyaWJ1dGlvbi5weQ==) | `93.89% <100%> (+0.19%)` | :arrow_up: |
| [momepy/intensity.py](https://codecov.io/gh/martinfleis/momepy/pull/54/diff?src=pr&el=tree#diff-bW9tZXB5L2ludGVuc2l0eS5weQ==) | `88.82% <100%> (+0.77%)` | :arrow_up: |
| [momepy/diversity.py](https://codecov.io/gh/martinfleis/momepy/pull/54/diff?src=pr&el=tree#diff-bW9tZXB5L2RpdmVyc2l0eS5weQ==) | `78.63% <0%> (-7.7%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/martinfleis/momepy/pull/54?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/54?src=pr&el=footer). Last update [be76722...6fe2845](https://codecov.io/gh/martinfleis/momepy/pull/54?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/momepy/distribution.py b/momepy/distribution.py
index 4e61e88..cfd1f81 100644
--- a/momepy/distribution.py
+++ b/momepy/distribution.py
@@ -182,13 +182,14 @@ def shared_walls_ratio(gdf, unique_id, perimeters=None):
     return series
 
 
-def street_alignment(left, right, orientations, left_network_id, right_network_id):
+def street_alignment(left, right, orientations, network_id=None, left_network_id=None, right_network_id=None):
     """
     Calculate the difference between street orientation and orientation of object in degrees
 
     Orientation of street segment is represented by the orientation of line
     connecting first and last point of the segment. Network ID linking each object
     to specific street segment is needed. Can be generated by :py:func:`momepy.get_network_id`.
+    Either network_id or left_network_id and right_network_id are required.
 
     .. math::
         \\left|{\\textit{building orientation} - \\textit{street orientation}}\\right|
@@ -202,9 +203,11 @@ def street_alignment(left, right, orientations, left_network_id, right_network_i
     orientations : str, list, np.array, pd.Series
         the name of the dataframe column, np.array, or pd.Series where is stored object orientation value
         (can be calculated using :py:func:`momepy.orientation`)
-    left_network_id : str, list, np.array, pd.Series
+    network_id : str (default None)
+        the name of the column storing network ID in both left and right
+    left_network_id : str, list, np.array, pd.Series (default None)
         the name of the left dataframe column, np.array, or pd.Series where is stored object network ID
-    right_network_id : str, list, np.array, pd.Series
+    right_network_id : str, list, np.array, pd.Series (default None)
         the name of the right dataframe column, np.array, or pd.Series of streets with unique network id (has to be defined beforehand)
         (can be defined using :py:func:`momepy.elements.unique_id`)
 
@@ -228,6 +231,18 @@ def street_alignment(left, right, orientations, left_network_id, right_network_i
     print('Calculating street alignments...')
     left = left.copy()
     right = right.copy()
+
+    if network_id:
+        left_network_id = network_id
+        right_network_id = network_id
+    else:
+        if left_network_id is None and right_network_id is not None:
+            raise ValueError('left_network_id not set.')
+        if left_network_id is not None and right_network_id is None:
+            raise ValueError('right_network_id not set.')
+        if left_network_id is None and right_network_id is None:
+            raise ValueError('Network ID not set. Use either network_id or left_network_id and right_network_id.')
+
     if not isinstance(orientations, str):
         left['mm_o'] = orientations
         orientations = 'mm_o'
diff --git a/momepy/intensity.py b/momepy/intensity.py
index 8f004cf..1abc32c 100644
--- a/momepy/intensity.py
+++ b/momepy/intensity.py
@@ -10,10 +10,12 @@ import numpy as np
 import collections
 
 
-def object_area_ratio(left, right, left_areas, right_areas, unique_id):
+def object_area_ratio(left, right, left_areas, right_areas, unique_id=None, left_unique_id=None, right_unique_id=None):
     """
     Calculate covered area ratio or floor area ratio of objects.
 
+    Either unique_id or left_unique_id and right_unique_id are required.
+
     .. math::
         \\textit{covering object area} \\over \\textit{covered object area}
 
@@ -28,9 +30,13 @@ def object_area_ratio(left, right, left_areas, right_areas, unique_id):
     right_areas : str, list, np.array, pd.Series
         the name of the right dataframe column, np.array, or pd.Series where is stored area value
         representing either projected or floor area.
-    unique_id : str
+    unique_id : str (default None)
         name of the column with unique id shared amongst left and right gdfs.
         If there is none, it could be generated by :py:func:'momepy.unique_id()'.
+    left_unique_id : str, list, np.array, pd.Series (default None)
+        the name of the left dataframe column, np.array, or pd.Series where is stored shared unique ID
+    right_unique_id : str, list, np.array, pd.Series (default None)
+        the name of the left dataframe column, np.array, or pd.Series where is stored shared unique ID
 
     Returns
     -------
@@ -50,6 +56,19 @@ def object_area_ratio(left, right, left_areas, right_areas, unique_id):
     Calculating OAR...
     Object area ratio calculated.
     """
+    left = left.copy()
+    right = right.copy()
+
+    if unique_id:
+        left_unique_id = unique_id
+        right_unique_id = unique_id
+    else:
+        if left_unique_id is None and right_unique_id is not None:
+            raise ValueError('left_unique_id not set.')
+        if left_unique_id is not None and right_unique_id is None:
+            raise ValueError('right_network_id not set.')
+        if left_unique_id is None and right_unique_id is None:
+            raise ValueError('Unique ID not set. Use either network_id or left_unique_id and right_unique_id.')
 
     print('Calculating object area ratio...')
     if not isinstance(left_areas, str):
@@ -60,9 +79,9 @@ def object_area_ratio(left, right, left_areas, right_areas, unique_id):
         right_areas = 'mm_a'
 
     print('Merging DataFrames...')
-    look_for = right[[unique_id, right_areas]]  # keeping only necessary columns
+    look_for = right[[right_unique_id, right_areas]]  # keeping only necessary columns
     look_for.rename(index=str, columns={right_areas: 'lf_area'}, inplace=True)
-    objects_merged = left[[unique_id, left_areas]].merge(look_for, on=unique_id)  # merging dataframes together
+    objects_merged = left[[left_unique_id, left_areas]].merge(look_for, left_on=left_unique_id, right_on=right_unique_id)
 
     print('Calculating OAR...')
 


Test patch:
diff --git a/tests/test_distribution.py b/tests/test_distribution.py
index 179fe32..cf27857 100644
--- a/tests/test_distribution.py
+++ b/tests/test_distribution.py
@@ -3,6 +3,8 @@ import geopandas as gpd
 import numpy as np
 from libpysal.weights import Queen
 
+import pytest
+
 
 class TestDistribution:
 
@@ -33,11 +35,21 @@ class TestDistribution:
 
     def test_street_alignment(self):
         self.df_buildings['orient'] = orient = mm.orientation(self.df_buildings)
-        self.df_buildings['street_alignment'] = mm.street_alignment(self.df_buildings, self.df_streets, 'orient', 'nID', 'nID')
+        self.df_buildings['street_alignment'] = mm.street_alignment(self.df_buildings, self.df_streets, 'orient', network_id='nID')
+        self.df_buildings['street_alignment2'] = mm.street_alignment(self.df_buildings, self.df_streets, 'orient',
+                                                                     left_network_id='nID', right_network_id='nID')
         self.df_buildings['street_a_arr'] = mm.street_alignment(self.df_buildings, self.df_streets, orient,
-                                                                self.df_buildings['nID'], self.df_streets['nID'])
+                                                                left_network_id=self.df_buildings['nID'], right_network_id=self.df_streets['nID'])
+
+        with pytest.raises(ValueError):
+            self.df_buildings['street_alignment'] = mm.street_alignment(self.df_buildings, self.df_streets, 'orient')
+        with pytest.raises(ValueError):
+            self.df_buildings['street_alignment'] = mm.street_alignment(self.df_buildings, self.df_streets, 'orient', left_network_id='nID')
+        with pytest.raises(ValueError):
+            self.df_buildings['street_alignment'] = mm.street_alignment(self.df_buildings, self.df_streets, 'orient', right_network_id='nID')
         check = 0.29073888476702336
         assert self.df_buildings['street_alignment'][0] == check
+        assert self.df_buildings['street_alignment2'][0] == check
         assert self.df_buildings['street_a_arr'][0] == check
 
     def test_cell_alignment(self):
diff --git a/tests/test_intensity.py b/tests/test_intensity.py
index 617784b..47ddc04 100644
--- a/tests/test_intensity.py
+++ b/tests/test_intensity.py
@@ -3,6 +3,8 @@ import geopandas as gpd
 import numpy as np
 from libpysal.weights import Queen
 
+import pytest
+
 
 class TestIntensity:
 
@@ -23,11 +25,19 @@ class TestIntensity:
 
     def test_object_area_ratio(self):
         car = mm.object_area_ratio(self.df_tessellation, self.df_buildings, 'area', 'area', 'uID')
+        carlr = mm.object_area_ratio(self.df_tessellation, self.df_buildings, 'area', 'area', left_unique_id='uID', right_unique_id='uID')
         check = 0.3206556897709747
         assert car.mean() == check
+        assert carlr.mean() == check
         far = mm.object_area_ratio(self.df_tessellation, self.df_buildings, 'area', 'fl_area', 'uID')
         check = 1.910949846262234
         assert far.mean() == check
+        with pytest.raises(ValueError):
+            car = mm.object_area_ratio(self.df_tessellation, self.df_buildings, 'area', 'area')
+        with pytest.raises(ValueError):
+            car = mm.object_area_ratio(self.df_tessellation, self.df_buildings, 'area', 'area', left_unique_id='uID')
+        with pytest.raises(ValueError):
+            car = mm.object_area_ratio(self.df_tessellation, self.df_buildings, 'area', 'area', right_unique_id='uID')
 
     def test_elements_count(self):
         eib = mm.elements_count(self.blocks, self.df_buildings, 'bID', 'bID')