Skip to content

Commit

Permalink
merge main
Browse files Browse the repository at this point in the history
  • Loading branch information
u3ks committed Jun 20, 2024
2 parents 29c3ef5 + 4f81d91 commit 8794f5c
Show file tree
Hide file tree
Showing 7 changed files with 248 additions and 92 deletions.
1 change: 1 addition & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ A set of functions to measure spatial diversity of elements and their values:
simpson
theil
values_range
mean_deviation

Note that additional diversity characters can be directly derived using :meth:`libpysal.graph.Graph.describe`.

Expand Down
3 changes: 2 additions & 1 deletion momepy/distribution.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from packaging.version import Version
from tqdm.auto import tqdm # progress bar

from .utils import _azimuth, deprecated
from .utils import _azimuth, deprecated, removed

__all__ = [
"Orientation",
Expand Down Expand Up @@ -681,6 +681,7 @@ def __init__(
self.series = pd.Series(results_list, index=gdf.index)


@removed("mean_deviation")
class NeighboringStreetOrientationDeviation:
"""
Calculate the mean deviation of solar orientation of adjacent streets. The
Expand Down
73 changes: 73 additions & 0 deletions momepy/functional/_diversity.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
)

__all__ = [
"mean_deviation",
"describe_agg",
"describe_reached_agg",
"values_range",
Expand Down Expand Up @@ -971,3 +972,75 @@ def percentile(
result = DataFrame(np.stack(stats), columns=q, index=stats.index)
result.loc[graph.isolates] = np.nan
return result


def mean_deviation(y: Series, graph: Graph) -> Series:
"""Calculate the mean deviation of each ``y`` value and its graph neighbours.
.. math::
\\frac{1}{n}\\sum_{i=1}^n dev_i=\\frac{dev_1+dev_2+\\cdots+dev_n}{n}
Parameters
----------
y : Series
A Series containing the values to be analysed.
graph : libpysal.graph.Graph
Graph representing spatial relationships between elements.
Returns
-------
Series
Examples
--------
>>> from libpysal import graph
>>> path = momepy.datasets.get_path("bubenec")
>>> buildings = geopandas.read_file(path, layer="buildings")
>>> buildings.head()
uID geometry
0 1 POLYGON ((1603599.221 6464369.816, 1603602.984...
1 2 POLYGON ((1603042.88 6464261.498, 1603038.961 ...
2 3 POLYGON ((1603044.65 6464178.035, 1603049.192 ...
3 4 POLYGON ((1603036.557 6464141.467, 1603036.969...
4 5 POLYGON ((1603082.387 6464142.022, 1603081.574...
Define spatial graph:
>>> knn5 = graph.Graph.build_knn(buildings.centroid, k=5)
>>> knn5
<Graph of 144 nodes and 720 nonzero edges indexed by
[0, 1, 2, 3, 4, ...]>
Mean deviation of building area and area of 5 nearest neighbors:
>>> momepy.mean_deviation(buildings.area, knn5)
0 281.179149
1 10515.948995
2 2240.706061
3 230.360732
4 68.719810
...
139 259.180720
140 517.496703
141 331.849751
142 25.297225
143 654.691897
Length: 144, dtype: float64
"""

inp = graph._adjacency.index.get_level_values(0)
res = graph._adjacency.index.get_level_values(1)

itself = inp == res
inp = inp[~itself]
res = res[~itself]

left = y.loc[inp].reset_index(drop=True)
right = y.loc[res].reset_index(drop=True)
deviations = (left - right).abs()

vals = deviations.groupby(inp).mean()

result = Series(np.nan, index=y.index)
result.loc[vals.index] = vals.values
return result
8 changes: 7 additions & 1 deletion momepy/functional/_elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,10 +314,16 @@ def _tess(ix, poly, blg, threshold, shrink, segment, enclosure_id):
tess[enclosure_id] = ix
return tess

## in case a single building is left in blg
if len(blg) == 1:
assigned_ix = blg.index[0]
else:
assigned_ix = -1

return GeoDataFrame(
{enclosure_id: ix},
geometry=[poly],
index=[-1],
index=[assigned_ix],
crs=blg.crs,
)

Expand Down
22 changes: 22 additions & 0 deletions momepy/functional/tests/test_diversity.py
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,18 @@ def test_count_unique_using_describe(self):
check_names=False,
)

def test_mean_deviation(self):
street_graph = Graph.build_contiguity(self.df_streets, rook=False)
y = mm.orientation(self.df_streets)
deviations = mm.mean_deviation(y, street_graph)
expected = {
"count": 35,
"mean": 7.527840590385933,
"min": 0.00798704765839,
"max": 20.9076846002,
}
assert_result(deviations, expected, self.df_streets)


class TestDescribeEquality:
def setup_method(self):
Expand Down Expand Up @@ -1165,3 +1177,13 @@ def test_distance_decay_linearly_weighted_percentiles(self):
verbose=False,
).frame
assert_frame_equal(perc_new, perc_old, check_dtype=False, check_names=False)

def test_mean_deviation(self):
street_graph = Graph.build_contiguity(self.df_streets, rook=False)
y = mm.orientation(self.df_streets)
deviations_new = mm.mean_deviation(y, street_graph)
deviations_old = mm.NeighboringStreetOrientationDeviation(
self.df_streets
).series

assert_series_equal(deviations_new, deviations_old, check_names=False)
23 changes: 23 additions & 0 deletions momepy/functional/tests/test_elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,29 @@ def test_multi_index(self):
):
mm.generate_blocks(buildings, self.enclosures, self.enclosures)

def test_tess_single_building_edge_case(self):
tessellations = mm.enclosed_tessellation(
self.df_buildings, self.enclosures.geometry, n_jobs=-1
)
orig_grouper = tessellations.groupby("enclosure_index")
idxs = ~self.df_buildings.index.isin(orig_grouper.get_group(8).index)
idxs[1] = True
idxs[21] = False
idxs[23] = False

new_blg = self.df_buildings[idxs]
new_blg.loc[22, "geometry"] = new_blg.loc[22, "geometry"].buffer(20)
new_tess = mm.enclosed_tessellation(new_blg, self.enclosures.geometry, n_jobs=1)

# assert that buildings 1 and 22 intersect the same enclosure
inp, res = self.enclosures.sindex.query(
new_blg.geometry, predicate="intersects"
)
assert np.isclose(new_blg.iloc[inp[res == 8]].index.values, [1, 22]).all()

# assert that there is a tessellation for building 1
assert 1 in new_tess.index


class TestElementsEquivalence:
def setup_method(self):
Expand Down
Loading

0 comments on commit 8794f5c

Please sign in to comment.