Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DOC: add migration guide to new API #637

Merged
merged 4 commits into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/user_guide/intro.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ Notebooks cover just a small selection of functions as an illustration of princi
Using spatial weights matrix <weights/weights>
Street network analysis <graph/graph>
Data preprocessing <preprocessing/preprocessing>
migration
306 changes: 306 additions & 0 deletions docs/user_guide/migration.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,306 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Migration to momepy 1.0 API\n",
"\n",
"Starting with the version 0.8, momepy contains completely new API (and refactored internals) that will become the only API in the momepy 1.0. Given this is a complete reimplemnation of nearly entirety of the package, it is not compatible with the legacy class-based API. This notebook contains a succint migration guide, highlighiting the key differences between the two APIs and outlines required changes to existing code to make it compatible with upcoming 1.0 release."
martinfleis marked this conversation as resolved.
Show resolved Hide resolved
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import geopandas as gpd\n",
"from libpysal import graph, weights\n",
"\n",
"import momepy\n",
"\n",
"buildings = gpd.read_file(\n",
" momepy.datasets.get_path(\"bubenec\"), layer=\"buildings\"\n",
")\n",
"tessellation = gpd.read_file(\n",
" momepy.datasets.get_path(\"bubenec\"), layer=\"tessellation\"\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Functions over classes\n",
"\n",
"The first key difference you may notice is that vast majority of functionality is now offered in a form of functions, rather than classes. Take an example fo equivalent rectangular index and its asssignemnt as a new column.\n",
martinfleis marked this conversation as resolved.
Show resolved Hide resolved
"\n",
"The new API is simple:"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/Users/martin/miniforge3/envs/momepy/lib/python3.11/site-packages/pandas/core/arraylike.py:492: RuntimeWarning: invalid value encountered in oriented_envelope\n",
" return getattr(ufunc, method)(*new_inputs, **kwargs)\n"
]
}
],
"source": [
"buildings[\"eri\"] = momepy.equivalent_rectangular_index(buildings)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The old API, which furhter required extracting the series from the class:"
martinfleis marked this conversation as resolved.
Show resolved Hide resolved
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/var/folders/2f/fhks6w_d0k556plcv3rfmshw0000gn/T/ipykernel_47459/3862675108.py:1: FutureWarning: Class based API like `momepy.EquivalentRectangularIndex` is deprecated. Replace it with `momepy.equivalent_rectangular_index` to use functional API instead or pin momepy version <1.0. Class-based API will be removed in 1.0. \n",
" buildings[\"eri\"] = momepy.EquivalentRectangularIndex(buildings).series\n",
"/Users/martin/miniforge3/envs/momepy/lib/python3.11/site-packages/pandas/core/arraylike.py:492: RuntimeWarning: invalid value encountered in oriented_envelope\n",
" return getattr(ufunc, method)(*new_inputs, **kwargs)\n"
]
}
],
"source": [
"buildings[\"eri\"] = momepy.EquivalentRectangularIndex(buildings).series"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"When running the code, you will see a warning about the deprecation.\n",
"\n",
"```\n",
"FutureWarning: Class based API like `momepy.EquivalentRectangularIndex` is deprecated. Replace it with `momepy.equivalent_rectangular_index` to use functional API instead or pin momepy version <1.0. Class-based API will be removed in 1.0.\n",
"```\n",
"\n",
"If there is a direct equivalent, it also tells you its name. In some cases, there is no equivalent in `momepy` but is elsewhere. \n",
martinfleis marked this conversation as resolved.
Show resolved Hide resolved
"\n",
"Measuring area with new API will requiring using `geopandas` directly."
martinfleis marked this conversation as resolved.
Show resolved Hide resolved
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"buildings['area'] = buildings.area"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"While the legacy API offered a wrapper."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/var/folders/2f/fhks6w_d0k556plcv3rfmshw0000gn/T/ipykernel_47459/1190336779.py:1: FutureWarning: `momepy.Area` is deprecated. Replace it with `.area` attribute of a GeoDataFrame or pin momepy version <1.0. This class will be removed in 1.0. \n",
" buildings['area'] = momepy.Area(buildings).series\n"
]
}
],
"source": [
"buildings['area'] = momepy.Area(buildings).series"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The warning is a bit different but still provides a guidance.\n",
martinfleis marked this conversation as resolved.
Show resolved Hide resolved
"\n",
"```\n",
"FutureWarning: `momepy.Area` is deprecated. Replace it with `.area` attribute of a GeoDataFrame or pin momepy version <1.0. This class will be removed in 1.0.\n",
"```\n",
"\n",
"## Dependency on libpysal `Graph` over `W`\n",
"\n",
"Spatial relationship in the legacy API is represented using `libpysal.weights.W` object. In the new one, `momepy` depends on new `libpysal.graph.Graph` implementation. That has two consequences - different ways of building the object and reliance on `GeoDataFrame` index.\n",
martinfleis marked this conversation as resolved.
Show resolved Hide resolved
"\n",
"`Graph` encodes geoemtries using the index they have in the `GeoDataFrame`. It does not use positional indexing nor custom column. The two objects are tied together via the index. For momepy, it means that index plays a central role in the implementation and there's no `\"unique_id\"` column any longer. Its role takes index.\n",
martinfleis marked this conversation as resolved.
Show resolved Hide resolved
"\n",
"Exmple of omputing a number of neighbors relative to the perimeter of each geometry using the new API:"
martinfleis marked this conversation as resolved.
Show resolved Hide resolved
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"# build contiguity of order 2\n",
"contiguity_k2 = graph.Graph.build_contiguity(tessellation).higher_order(2, lower_order=True)\n",
"\n",
"# measure neighbors\n",
"tessellation[\"neighbours_weighted\"] = momepy.neighbors(\n",
" tessellation, contiguity_k2, weighted=True\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"And using the old API:"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/var/folders/2f/fhks6w_d0k556plcv3rfmshw0000gn/T/ipykernel_47459/1307576710.py:1: FutureWarning: `momepy.sw_high` is deprecated. Replace it with .higher_order() method of libpysal.graph.Graph or pin momepy version <1.0. This class will be removed in 1.0. \n",
" contiguity_k2 = momepy.sw_high(k=2, gdf=tessellation, ids='uID')\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"/var/folders/2f/fhks6w_d0k556plcv3rfmshw0000gn/T/ipykernel_47459/1307576710.py:3: FutureWarning: Class based API like `momepy.Neighbors` is deprecated. Replace it with `momepy.neighbors` to use functional API instead or pin momepy version <1.0. Class-based API will be removed in 1.0. \n",
" tessellation['neighbours'] = momepy.Neighbors(tessellation, contiguity_k2,'uID', weighted=True).series\n"
]
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "44f9ec7d1da54ae794472e6669c9c458",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
" 0%| | 0/144 [00:00<?, ?it/s]"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"contiguity_k2 = momepy.sw_high(k=2, gdf=tessellation, ids='uID')\n",
"\n",
"tessellation['neighbours'] = momepy.Neighbors(tessellation, contiguity_k2,'uID', weighted=True).series"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note that the `sw_high` function alowed bulding contiguity only.\n",
martinfleis marked this conversation as resolved.
Show resolved Hide resolved
"\n",
"## Reliance on index\n",
"\n",
"When we need to capture relationship between two objects (e.g., `GeoDataFrame` and its `Graph`), the primary method is to rely on index. Unlike momepy 0.7, which heavily depends on columns with IDs mapping rows of one GeoDataFrame to the other, new API attempts to minimise use of such columns. Below is the overview of the logic used in various situations.\n",
martinfleis marked this conversation as resolved.
Show resolved Hide resolved
"\n",
"### Geometry and Graph\n",
"\n",
"This case is easy. `Graph` is mapped to geometry (either `GeoSeries` or `GeoDataFrame`) via index of the GeoPandas object. \n",
martinfleis marked this conversation as resolved.
Show resolved Hide resolved
"\n",
"```py\n",
"contiguity = graph.Graph.build_contiguity(geometry)\n",
"momepy.neighbor_distance(geometry, contiguity)\n",
"```\n",
"\n",
"In this case, you shall ensure that the index of `geometry` does not change and, in some cases, that the order of rows is also preserved to ensure the mapping of values to sparse arrays is not mismatched.\n",
martinfleis marked this conversation as resolved.
Show resolved Hide resolved
"\n",
"### Series and Graph\n",
"\n",
"A subset of the case above is linking of a `pandas.Series` to the `Graph`. Such a situation assumes that the index of the `Series` is equal to the index of the original geometry from which the `Graph` was created.\n",
martinfleis marked this conversation as resolved.
Show resolved Hide resolved
"\n",
"```py\n",
"# typically, the Series is taken directly from the DataFrame\n",
"contiguity = graph.Graph.build_contiguity(geometry)\n",
"momepy.alignment(geometry[\"orientation\"], contiguity)\n",
"```\n",
"\n",
"### Geometry and two Graphs\n",
"\n",
"Another subset is when you need to link geometry to two Graphs. In that case, both Graphs need to be based on the same index.\n",
"\n",
"```py\n",
"adjacency_graph = graph.Graph.build_contiguity(geometry)\n",
"neighborhood_graph = graph.Graph.build_distance_band(\n",
"\tgeometry, \n",
"\tthreshold=400,\n",
")\n",
"momepy.mean_interbuilding_distance(\n",
"\tgeometry, \n",
"\tadjacency_graph, \n",
"\tneighborhood_graph,\n",
")\n",
"```\n",
"\n",
"## Geometry and Geometry\n",
"\n",
"When linking two geometry arrays together, which can be for example a used to capture which building belongs to which street segment, or which building belongs to which block/enclosure, you cannot rely solely on indices as the two objects do not match. In this situation, momepy will use the index of one Series, typically the shorter one, and a Series (i.e. a column) in another. \n",
martinfleis marked this conversation as resolved.
Show resolved Hide resolved
"\n",
"```py\n",
"buildings[\"street_index\"] = momepy.get_nearest_street(\n",
"\tbuildings, \n",
"\tstreet_edges,\n",
")\n",
"momepy.street_alignment(\n",
"\tbuildings[\"orientation\"], \n",
"\tstreet_edges[\"orientation\"], \n",
"\tnetwork_id=\"street_index\",\n",
")\n",
"```"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "pysal",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
Loading