{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Segregation Index Decomposition" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This notebook walks through the decomposition framework of the PySAL *segregation* package, which breaks the difference between two comparative segregation measures into a **spatial component** (`c_s`) and an **attribute component** (`c_a`), following *Rey, S. et al. \"Comparative Spatial Segregation Analytics\"*.\n", "\n", "To keep the example self-contained we use the Sacramento demonstration dataset bundled with `libpysal` and compare segregation between the western and eastern halves of the region." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Table of Contents\n", "* [Data preparation](#Data-preparation)\n", "* [Composition Approach (default)](#Composition-Approach-(default))\n", "* [Share Approach](#Share-Approach)\n", "* [Dual Composition Approach](#Dual-Composition-Approach)\n", "* [Inspecting a different index: Relative Concentration](#Inspecting-a-different-index:-Relative-Concentration)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Data preparation" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "First, import the needed libraries." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import geopandas as gpd\n", "import matplotlib.pyplot as plt\n", "\n", "from libpysal.examples import load_example\n", "from segregation.singlegroup import Gini, RelativeConcentration\n", "from segregation.decomposition import DecomposeSegregation" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Read the Sacramento tracts and reproject them into an appropriate projected CRS so that distance-based operations behave well." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sacramento = gpd.read_file(load_example(\"Sacramento1\").get_path(\"sacramentot2.shp\"))\n", "sacramento = sacramento.to_crs(sacramento.estimate_utm_crs())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We study the segregation of the non-Hispanic Black population (`BLACK`) relative to the total population (`TOT_POP`). To obtain two contexts to compare, we split the region into a western and an eastern half at the median tract-centroid longitude." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sacramento[\"cx\"] = sacramento.geometry.centroid.x\n", "split = sacramento[\"cx\"].median()\n", "\n", "west = sacramento.loc[sacramento[\"cx\"] <= split].copy()\n", "east = sacramento.loc[sacramento[\"cx\"] > split].copy()\n", "\n", "len(west), len(east)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The composition of the focal group in each context:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, axes = plt.subplots(1, 2, figsize=(12, 5))\n", "for ax, gdf, title in zip(axes, [west, east], [\"West\", \"East\"]):\n", " gdf[\"composition\"] = np.where(gdf[\"TOT_POP\"] == 0, 0, gdf[\"BLACK\"] / gdf[\"TOT_POP\"])\n", " gdf.plot(column=\"composition\", cmap=\"OrRd\", legend=True, ax=ax)\n", " ax.set_title(f\"Composition, {title}\")\n", " ax.axis(\"off\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We first compare the Gini segregation index of both contexts and check the difference in point estimates." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "G_west = Gini(west, \"BLACK\", \"TOT_POP\")\n", "G_east = Gini(east, \"BLACK\", \"TOT_POP\")\n", "\n", "G_west.statistic - G_east.statistic" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The options available for the decomposition are documented on the `DecomposeSegregation` class:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "help(DecomposeSegregation)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Composition Approach (default)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The difference fitted above can be decomposed into a spatial component (`c_s`) and an attribute component (`c_a`). Let's estimate both." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "DS_composition = DecomposeSegregation(G_west, G_east)\n", "DS_composition.c_s" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "DS_composition.c_a" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Whichever component has the larger absolute value contributes more to the observed difference. The difference in composition can be inspected with the `cdfs` plot type:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "DS_composition.plot(plot_type=\"cdfs\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When the underlying data are GeoDataFrames, the counterfactual compositions can also be mapped. The first and second contexts are West and East, respectively." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "DS_composition.plot(plot_type=\"maps\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*In every plotting method, the title reports each component of the decomposition.*" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Share Approach" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `share` approach builds the counterfactual total population of each unit from the share of both the focal and the complementary group in each context." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "DS_share = DecomposeSegregation(G_west, G_east, counterfactual_approach=\"share\")\n", "DS_share.plot(plot_type=\"cdfs\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "DS_share.plot(plot_type=\"maps\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Dual Composition Approach" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `dual_composition` approach is similar to the default composition approach, but it also uses the counterfactual composition of the complementary group." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "DS_dual = DecomposeSegregation(G_west, G_east, counterfactual_approach=\"dual_composition\")\n", "DS_dual.plot(plot_type=\"cdfs\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "DS_dual.plot(plot_type=\"maps\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Inspecting a different index: Relative Concentration" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The decomposition works with any comparative pair of indices of the same type. Here we repeat it with the spatial `RelativeConcentration` index, where the spatial component typically plays a larger role." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "RCO_west = RelativeConcentration(west, \"BLACK\", \"TOT_POP\")\n", "RCO_east = RelativeConcentration(east, \"BLACK\", \"TOT_POP\")\n", "\n", "RCO_west.statistic - RCO_east.statistic" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "RCO_decomp = DecomposeSegregation(RCO_west, RCO_east)\n", "RCO_decomp.c_s" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "RCO_decomp.c_a" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "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.12" } }, "nbformat": 4, "nbformat_minor": 4 }