Repository: martinfleis/momepy
Pull request #58 (2019-08-13 13:35:24)

ENH: allow mode in average
mean_character changes to average_character and allows median and mode on top of mean.


Patch:
diff --git a/momepy/dimension.py b/momepy/dimension.py
index e727423..f4979a3 100644
--- a/momepy/dimension.py
+++ b/momepy/dimension.py
@@ -10,6 +10,7 @@ from .shape import _make_circle
 import pandas as pd
 import math
 import numpy as np
+import scipy as sp
 
 
 def area(gdf):
@@ -289,11 +290,12 @@ def longest_axis_length(gdf):
     return series
 
 
-def mean_character(gdf, values, spatial_weights, unique_id, rng=None):
+def average_character(gdf, values, spatial_weights, unique_id, rng=None, mode='mean'):
     """
-    Calculates the mean of a character within k steps of morphological tessellation
+    Calculates the average of a character within k steps of morphological tessellation
 
-    Mean value of the character within k topological steps defined in spatial_weights.
+    Average value of the character within k topological steps defined in spatial_weights.
+    Can be set to `mean`, `median` or `mode`.
 
     .. math::
         \\frac{1}{n}\\left(\\sum_{i=1}^{n} value_{i}\\right)
@@ -306,11 +308,13 @@ def mean_character(gdf, values, spatial_weights, unique_id, rng=None):
         the name of the dataframe column, np.array, or pd.Series where is stored character value.
     unique_id : str
         name of the column with unique id used as spatial_weights index.
-    spatial_weights : libpysal.weights, optional
+    spatial_weights : libpysal.weights
         spatial weights matrix
     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.
+    mode : str (default 'mean')
+        mode of average calculation. Can be set to `mean`, `median` or `mode`.
 
     Returns
     -------
@@ -325,17 +329,17 @@ def mean_character(gdf, values, spatial_weights, unique_id, rng=None):
     Examples
     --------
     >>> sw = libpysal.weights.DistanceBand.from_dataframe(tessellation, threshold=100, silence_warnings=True, ids='uID')
-    >>> tessellation['mesh_100'] = momepy.mean_character(tessellation, values='area', spatial_weights=sw, unique_id='uID')
-    Calculating mean character value...
+    >>> tessellation['mesh_100'] = momepy.average(tessellation, values='area', spatial_weights=sw, unique_id='uID')
+    Calculating average character value...
     100%|██████████| 144/144 [00:00<00:00, 1433.32it/s]
-    Mean character value calculated.
+    Average character value calculated.
     >>> tessellation.mesh_100[0]
     4823.1334436678835
     """
     # define empty list for results
     results_list = []
 
-    print('Calculating mean character value...')
+    print('Calculating average character value...')
     gdf = gdf.copy()
 
     if values is not None:
@@ -352,11 +356,18 @@ def mean_character(gdf, values, spatial_weights, unique_id, rng=None):
         if rng:
             from momepy import limit_range
             values_list = limit_range(values_list.tolist(), rng=rng)
-        results_list.append(np.mean(values_list))
+        if mode == 'mean':
+            results_list.append(np.mean(values_list))
+        elif mode == 'median':
+            results_list.append(np.median(values_list))
+        elif mode == 'mode':
+            results_list.append(sp.stats.mode(values_list)[0][0])
+        else:
+            raise ValueError('{} is not supported as mode.'.format(mode))
 
     series = pd.Series(results_list, index=gdf.index)
 
-    print('Mean character value calculated.')
+    print('Average character value calculated.')
     return series
 
 


Test patch:
diff --git a/tests/test_dimension.py b/tests/test_dimension.py
index 7c8e956..851b703 100644
--- a/tests/test_dimension.py
+++ b/tests/test_dimension.py
@@ -98,18 +98,19 @@ class TestDimensions:
         check = _make_circle(self.df_buildings.geometry[0].convex_hull.exterior.coords)[2] * 2
         assert self.df_buildings['long_axis'][0] == check
 
-    def test_mean_character(self):
+    def test_average_character(self):
         spatial_weights = Queen_higher(k=3, geodataframe=self.df_tessellation, ids='uID')
         self.df_tessellation['area'] = area = self.df_tessellation.geometry.area
-        self.df_tessellation['mesh_ar'] = mm.mean_character(self.df_tessellation, values='area', spatial_weights=spatial_weights, unique_id='uID')
-        self.df_tessellation['mesh_array'] = mm.mean_character(self.df_tessellation, values=area, spatial_weights=spatial_weights, unique_id='uID')
-        self.df_tessellation['mesh_id'] = mm.mean_character(self.df_tessellation, spatial_weights=spatial_weights,
-                                                            values='area', rng=(10, 90), unique_id='uID')
-        self.df_tessellation['mesh_iq'] = mm.mean_character(self.df_tessellation, spatial_weights=spatial_weights,
-                                                            values='area', rng=(25, 75), unique_id='uID')
-        check = 2922.9572601966815
-        assert self.df_tessellation['mesh_ar'][0] == check
-        assert self.df_tessellation['mesh_array'][0] == check
+        self.df_tessellation['mesh_ar'] = mm.average_character(self.df_tessellation, values='area', spatial_weights=spatial_weights,
+                                                               unique_id='uID', mode='mode')
+        self.df_tessellation['mesh_array'] = mm.average_character(self.df_tessellation, values=area, spatial_weights=spatial_weights,
+                                                                  unique_id='uID', mode='median')
+        self.df_tessellation['mesh_id'] = mm.average_character(self.df_tessellation, spatial_weights=spatial_weights,
+                                                               values='area', rng=(10, 90), unique_id='uID')
+        self.df_tessellation['mesh_iq'] = mm.average_character(self.df_tessellation, spatial_weights=spatial_weights,
+                                                               values='area', rng=(25, 75), unique_id='uID')
+        assert self.df_tessellation['mesh_ar'][0] == 249.50382416977067
+        assert self.df_tessellation['mesh_array'][0] == 2623.996266097268
         assert self.df_tessellation['mesh_id'][38] == 2250.2241176070806
         assert self.df_tessellation['mesh_iq'][38] == 2118.6091427330666