segstats 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. segstats-0.1.0/CHANGELOG.md +24 -0
  2. segstats-0.1.0/CITATION.cff +21 -0
  3. segstats-0.1.0/LICENSE +21 -0
  4. segstats-0.1.0/MANIFEST.in +9 -0
  5. segstats-0.1.0/PKG-INFO +282 -0
  6. segstats-0.1.0/README.md +244 -0
  7. segstats-0.1.0/examples/quickstart.py +69 -0
  8. segstats-0.1.0/pyproject.toml +116 -0
  9. segstats-0.1.0/setup.cfg +4 -0
  10. segstats-0.1.0/src/segstats/__init__.py +124 -0
  11. segstats-0.1.0/src/segstats/__main__.py +7 -0
  12. segstats-0.1.0/src/segstats/_arrays.py +84 -0
  13. segstats-0.1.0/src/segstats/_version.py +1 -0
  14. segstats-0.1.0/src/segstats/aspatial.py +203 -0
  15. segstats-0.1.0/src/segstats/cli.py +220 -0
  16. segstats-0.1.0/src/segstats/data.py +233 -0
  17. segstats-0.1.0/src/segstats/geometry.py +354 -0
  18. segstats-0.1.0/src/segstats/indices.py +327 -0
  19. segstats-0.1.0/src/segstats/inference.py +329 -0
  20. segstats-0.1.0/src/segstats/local.py +65 -0
  21. segstats-0.1.0/src/segstats/py.typed +0 -0
  22. segstats-0.1.0/src/segstats/spatial.py +375 -0
  23. segstats-0.1.0/src/segstats/weights.py +79 -0
  24. segstats-0.1.0/src/segstats.egg-info/PKG-INFO +282 -0
  25. segstats-0.1.0/src/segstats.egg-info/SOURCES.txt +36 -0
  26. segstats-0.1.0/src/segstats.egg-info/dependency_links.txt +1 -0
  27. segstats-0.1.0/src/segstats.egg-info/entry_points.txt +2 -0
  28. segstats-0.1.0/src/segstats.egg-info/requires.txt +10 -0
  29. segstats-0.1.0/src/segstats.egg-info/top_level.txt +1 -0
  30. segstats-0.1.0/tests/conftest.py +51 -0
  31. segstats-0.1.0/tests/test_aspatial.py +138 -0
  32. segstats-0.1.0/tests/test_cli.py +83 -0
  33. segstats-0.1.0/tests/test_data_local.py +122 -0
  34. segstats-0.1.0/tests/test_geometry.py +137 -0
  35. segstats-0.1.0/tests/test_indices.py +112 -0
  36. segstats-0.1.0/tests/test_inference.py +115 -0
  37. segstats-0.1.0/tests/test_reference_pysal.py +87 -0
  38. segstats-0.1.0/tests/test_spatial.py +164 -0
@@ -0,0 +1,24 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## [Unreleased]
6
+
7
+ ## [0.1.0] - 2026-11-09
8
+ ### Added
9
+ - Evenness: dissimilarity D, Gini, entropy (Theil H), Atkinson; multigroup D and H.
10
+ - Exposure: isolation xPx, interaction xPy, correlation ratio (eta squared), exposure matrix.
11
+ - Concentration: delta, absolute and relative concentration.
12
+ - Centralization: absolute and relative centralization (ties in distance grouped).
13
+ - Clustering: absolute clustering, relative clustering, spatial proximity, negative
14
+ exponential proximity matrix.
15
+ - Spatial evenness: Morrill D(adj), Wong D(w) and D(s), Reardon & O'Sullivan spatial
16
+ dissimilarity and spatial information theory index with kernel or contiguity
17
+ neighbourhoods.
18
+ - Local measures: location quotients, local entropy, local spatial entropy, per-zone
19
+ contributions to D.
20
+ - Pure-Python GeoJSON geometry: areas, perimeters, centroids, queen/rook contiguity and
21
+ shared boundary lengths (T-junctions handled), on-the-fly lon/lat projection.
22
+ - Index registry (`compute`, `profile`, `statistic`), random-allocation and zone-shuffle
23
+ permutation tests, multinomial and zone bootstrap confidence intervals.
24
+ - CSV / GeoJSON / pandas input and the `segstats` command-line tool.
@@ -0,0 +1,21 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use this software, please cite it as below."
3
+ authors:
4
+ - family-names: "Eminoğlu"
5
+ given-names: "Yusuf"
6
+ email: "geospacephilo@gmail.com"
7
+ orcid: "https://orcid.org/0009-0005-6000-2934"
8
+ title: "segstats"
9
+ version: "0.1.0"
10
+ date-released: "2026-11-09"
11
+ license: "MIT"
12
+ repository-code: "https://gitlab.com/geophilo1/segstats_sdk"
13
+ url: "https://pypi.org/project/segstats/"
14
+ abstract: "Residential segregation and diversity indices for urban analysis: the five Massey-Denton dimensions (evenness, exposure, concentration, centralization, clustering), multigroup indices, spatial evenness measures (Morrill, Wong, Reardon-O'Sullivan), local diversity measures, random-allocation permutation tests and bootstrap confidence intervals."
15
+ keywords:
16
+ - segregation
17
+ - residential segregation
18
+ - dissimilarity index
19
+ - entropy
20
+ - urban analysis
21
+ - spatial statistics
segstats-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yusuf Eminoğlu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,9 @@
1
+ include LICENSE
2
+ include README.md
3
+ include CHANGELOG.md
4
+ include CITATION.cff
5
+ include pyproject.toml
6
+ include src/segstats/py.typed
7
+ recursive-include src/segstats *.py
8
+ recursive-include tests *.py
9
+ recursive-include examples *.py
@@ -0,0 +1,282 @@
1
+ Metadata-Version: 2.4
2
+ Name: segstats
3
+ Version: 0.1.0
4
+ Summary: Residential segregation and diversity indices for urban analysis: the five Massey-Denton dimensions, multigroup and spatial indices (Reardon-O'Sullivan, Wong, Morrill), local measures, permutation tests and bootstrap CIs.
5
+ Author-email: Yusuf Eminoğlu <geospacephilo@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://gitlab.com/geophilo1/segstats_sdk
8
+ Project-URL: Repository, https://gitlab.com/geophilo1/segstats_sdk
9
+ Project-URL: Issues, https://gitlab.com/geophilo1/segstats_sdk/-/work_items
10
+ Project-URL: Changelog, https://gitlab.com/geophilo1/segstats_sdk/-/blob/main/CHANGELOG.md
11
+ Keywords: segregation,residential-segregation,dissimilarity-index,entropy,diversity,massey-denton,spatial-statistics,urban-analysis,urban-planning,census,geojson,gis
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Operating System :: OS Independent
20
+ Classifier: Environment :: Console
21
+ Classifier: Topic :: Scientific/Engineering :: GIS
22
+ Classifier: Topic :: Sociology
23
+ Classifier: Intended Audience :: Science/Research
24
+ Classifier: Intended Audience :: Developers
25
+ Classifier: Typing :: Typed
26
+ Requires-Python: >=3.9
27
+ Description-Content-Type: text/markdown
28
+ License-File: LICENSE
29
+ Requires-Dist: numpy>=1.22
30
+ Provides-Extra: pandas
31
+ Requires-Dist: pandas>=1.3.0; extra == "pandas"
32
+ Provides-Extra: dev
33
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
34
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
35
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
36
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
37
+ Dynamic: license-file
38
+
39
+ # segstats
40
+
41
+ [![PyPI version](https://img.shields.io/pypi/v/segstats.svg)](https://pypi.org/project/segstats/)
42
+ [![Python versions](https://img.shields.io/pypi/pyversions/segstats.svg)](https://pypi.org/project/segstats/)
43
+ [![pipeline](https://gitlab.com/geophilo1/segstats_sdk/badges/main/pipeline.svg)](https://gitlab.com/geophilo1/segstats_sdk/-/pipelines)
44
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
45
+
46
+ **Residential segregation and diversity indices for urban analysis.**
47
+
48
+ `segstats` computes the classic segregation indices from a table of population
49
+ counts by zone (census tracts, neighbourhoods, *mahalle*) and, when zone polygons are
50
+ available, their spatial counterparts. It covers the five dimensions of Massey &
51
+ Denton (1988), multigroup indices, spatially adjusted measures, local indices per
52
+ zone, and Monte Carlo inference. It needs only NumPy. Geometry comes from plain
53
+ GeoJSON; shapely and GDAL are not required.
54
+
55
+ ```bash
56
+ pip install segstats # numpy only
57
+ pip install "segstats[pandas]" # + DataFrame input
58
+ ```
59
+
60
+ Requires Python 3.9 or newer.
61
+
62
+ > **Looking for more?** The PySAL
63
+ > [`segregation`](https://github.com/pysal/segregation) package is a fuller
64
+ > alternative. It adds many more indices, decomposition, network distances,
65
+ > multiscalar profiles and comparative inference, but needs the full geospatial
66
+ > stack. `segstats` is a small, dependency-light toolkit whose numbers are checked
67
+ > against hand calculations and PySAL (see *Validation*).
68
+
69
+ ---
70
+
71
+ ## Quickstart
72
+
73
+ ```python
74
+ import segstats as ss
75
+
76
+ # counts only: CSV with columns tract, black, total
77
+ data = ss.read_csv("tracts.csv", id_column="tract", groups=["black"], total_column="total")
78
+ x, t = data.two_group("black") # group counts and zone totals
79
+ ss.dissimilarity(x, t) # 0 = even, 1 = complete segregation
80
+ ss.profile(data, group="black") # every aspatial index at once
81
+
82
+ # counts + polygons (GeoJSON in lon/lat is projected to metres on the fly)
83
+ data = ss.read_geojson("tracts.geojson", id_property="GEOID", counts=data)
84
+ ss.profile(data, group="black", center=(28.98, 41.01), bandwidth=1000)
85
+
86
+ # Is D higher than random allocation would give?
87
+ res = ss.permutation_test(data, "D", group="black", n_permutations=999, seed=1)
88
+ res.statistic, res.null_mean, res.p_value
89
+
90
+ # 95% bootstrap CI of the spatial information theory index (1 km Gaussian kernel)
91
+ ci = ss.bootstrap_ci(data, "SH", bandwidth=1000, seed=1)
92
+ ci.ci_low, ci.ci_high
93
+
94
+ # local measures
95
+ ss.location_quotient(data.counts) # zones x groups
96
+ ss.local_entropy(data.counts) # normalised diversity per zone
97
+ ```
98
+
99
+ See [`examples/quickstart.py`](examples/quickstart.py) for a runnable synthetic city.
100
+
101
+ ### Command line
102
+
103
+ ```bash
104
+ segstats list # all indices and their inputs
105
+ segstats indices tracts.csv --id tract --groups black --total total --group black
106
+ segstats indices tracts.csv --id tract --groups black --total total \
107
+ --geojson tracts.geojson --geo-id GEOID --group black \
108
+ --center 28.98,41.01 --bandwidth 1000 --permutations 999 --bootstrap 999 --seed 1
109
+ segstats local tracts.geojson --id GEOID --groups black,white,asian -o local.csv
110
+ ```
111
+
112
+ `--format table|json|csv` controls the output. A GeoJSON file can hold the counts in
113
+ its feature properties, or a CSV table can be joined to polygons with `--geojson`.
114
+
115
+ ---
116
+
117
+ ## Indices
118
+
119
+ Two-group indices compare a group of interest (*x*) with everyone else
120
+ (*y = t - x*). Multigroup indices use all count columns.
121
+
122
+ | Name | Index | Dimension | Inputs | Source |
123
+ |---|---|---|---|---|
124
+ | `D` | Dissimilarity | evenness | counts | Duncan & Duncan 1955 |
125
+ | `G` | Gini | evenness | counts | Massey & Denton 1988 |
126
+ | `H` | Entropy / Theil H | evenness | counts | Theil & Finizza 1971 |
127
+ | `A` | Atkinson (shape *b*) | evenness | counts | Atkinson 1970 |
128
+ | `MD`, `MH` | Multigroup D and H | evenness | counts | Reardon & Firebaugh 2002 |
129
+ | `xPx` | Isolation | exposure | counts | Bell 1954; M&D 1988 |
130
+ | `xPy` | Interaction | exposure | counts | M&D 1988 |
131
+ | `V` | Correlation ratio (eta squared) | exposure | counts | Bell 1954; White 1986 |
132
+ | `DEL` | Delta | concentration | area | Duncan, Cuzzort & Duncan 1961 |
133
+ | `ACO`, `RCO` | Absolute / relative concentration | concentration | area | M&D 1988 |
134
+ | `ACE`, `RCE` | Absolute / relative centralization | centralization | centre | M&D 1988 |
135
+ | `ACL`, `RCL` | Absolute / relative clustering | clustering | distances | M&D 1988 |
136
+ | `SP` | Spatial proximity | clustering | distances | White 1983 |
137
+ | `D_adj` | Adjusted D (contiguity) | spatial evenness | contiguity | Morrill 1991 |
138
+ | `D_w`, `D_s` | Boundary-weighted and shape-adjusted D | spatial evenness | boundaries | Wong 1993 |
139
+ | `SD`, `SH` | Spatial dissimilarity and information theory index | spatial evenness | neighbourhood | Reardon & O'Sullivan 2004 |
140
+
141
+ Local indices: `location_quotient`, `local_entropy`, `local_spatial_entropy`
142
+ (entropy of each zone's kernel-weighted local environment), and
143
+ `local_dissimilarity_contribution`.
144
+
145
+ ### API overview
146
+
147
+ | Area | Functions |
148
+ |---|---|
149
+ | Plain arrays | `dissimilarity(x, t)`, `gini`, `entropy`, `atkinson(x, t, b)`, `isolation`, `interaction`, `correlation_ratio`, `multigroup_dissimilarity(counts)`, `multigroup_entropy`, `exposure_matrix`, `delta(x, t, area)`, `absolute_concentration`, `relative_concentration`, `absolute_centralization(x, area, dist)`, `relative_centralization(x, t, dist)`, `absolute_clustering(x, t, c)`, `relative_clustering`, `spatial_proximity`, `proximity_matrix(d, area, scale)`, `morrill_dissimilarity(x, t, C)`, `wong_dissimilarity_w(x, t, B)`, `wong_dissimilarity_s(x, t, B, perimeter, area)`, `local_environment(counts, W)`, `spatial_dissimilarity(counts, W)`, `spatial_entropy(counts, W)` |
150
+ | Data | `ZoneData`, `read_csv`, `read_geojson`, `from_dataframe`, `ZoneGeometry` (`area`, `perimeter`, `centroids`, `contiguity("queen"/"rook")`, `boundary_lengths()`, `distances()`) |
151
+ | Weights | `distance_matrix`, `kernel_weights(d, bandwidth, kernel)`, `with_self`, `row_standardize` |
152
+ | By name | `compute(data, "D", group)`, `profile(data, group)`, `statistic(...)`, `INDICES`, `IndexOptions` |
153
+ | Inference | `permutation_test(data, index, group, null="labels"/"zones")`, `bootstrap_ci(data, index, group, method="multinomial"/"zones")`, `random_allocation` |
154
+
155
+ `IndexOptions` holds the options shared by the registry: Atkinson `b`, `center` or
156
+ `center_distance`, `decay_scale` (distance unit of `exp(-d)`, default 1000 for km
157
+ when coordinates are metres), `clustering` (`"proximity"` or `"contiguity"` matrix for
158
+ ACL), `bandwidth` and `kernel` (`gaussian`, `exponential`, `biweight`, `box`) for the
159
+ Reardon & O'Sullivan neighbourhood (default: the zone plus its contiguous
160
+ neighbours), and `contiguity` (`queen`/`rook`).
161
+
162
+ ---
163
+
164
+ ## Method and sources
165
+
166
+ Notation: *x_i*, *y_i*, *t_i* are the group, rest-of-population and total counts of
167
+ zone *i*; capitals are city totals; *p_i = x_i / t_i*, *P = X / T*; *a_i* is the zone
168
+ area.
169
+
170
+ - **D** = ½ Σ |x_i/X − y_i/Y|. **G** = ΣΣ t_i t_j |p_i − p_j| / (2T²P(1−P)), computed
171
+ in O(n log n) from sorted proportions. **H** = Σ t_i (E − E_i) / (E T) with Shannon
172
+ entropies E, E_i (natural log). **A** = 1 − P/(1−P) · |Σ (1−p_i)^(1−b) p_i^b t_i /
173
+ (P T)|^(1/(1−b)). (Massey & Denton 1988, eqs. 1–4.)
174
+ - **xPx** = Σ (x_i/X)(x_i/t_i), **xPy** = Σ (x_i/X)(y_i/t_i), **V** = (xPx − P)/(1 − P).
175
+ - **Multigroup** D = Σ_m Σ_i t_i |π_im − π_m| / (2 T I), with I = Σ π_m(1 − π_m), and
176
+ Theil's H (Reardon & Firebaugh 2002).
177
+ - **DEL** = ½ Σ |x_i/X − a_i/A|. **ACO** and **RCO** compare the group's
178
+ area-weighted mean zone size with that of the n₁ smallest and the n₂ largest zones
179
+ holding X people (Massey & Denton 1988, eqs. 10–11; same convention for n₁ and n₂
180
+ as PySAL).
181
+ - **ACE** = Σ X_{i−1}A_i − Σ X_i A_{i−1} and **RCE** = Σ X_{i−1}Y_i − Σ X_i Y_{i−1} over
182
+ zones sorted by distance to the centre (cumulative shares). Zones at equal distance
183
+ form one step, so the result does not depend on input order.
184
+ - **SP** = (X Pxx + Y Pyy)/(T Ptt), **RCL** = Pxx/Pyy − 1, with Pxx = ΣΣ x_i x_j c_ij /
185
+ X² and c_ij = exp(−d_ij / scale), d_ii = (0.6 a_i)^0.5 (Massey & Denton 1988; White
186
+ 1983). **ACL** follows Massey & Denton eq. 14 with the chosen c matrix.
187
+ - **D(adj)** = D − ΣΣ c_ij |p_i − p_j| / ΣΣ c_ij (binary contiguity; Morrill 1991).
188
+ **D(w)** = D − ΣΣ w_ij |p_i − p_j| with w_ij = b_ij / ΣΣ b_kl, the normalised
189
+ shared-boundary length. **D(s)** additionally multiplies each term by
190
+ ½(PA_i + PA_j)/max(PA), PA = perimeter/area (Wong 1993). The normalisation follows
191
+ the R package `seg` (Hong, O'Sullivan & Sadahiro 2014). Zones with no population are
192
+ left out of the adjustment term.
193
+ - **Spatial D and H** (Reardon & O'Sullivan 2004): each zone's local environment
194
+ is π̃_im = Σ_j w_ij x_jm / Σ_j w_ij t_j, with kernel weights that include the
195
+ zone itself. Then H̃ = 1 − Σ t_i Ẽ_i / (T E) and D̃ = Σ_m Σ_i t_i |π̃_im − π_m| /
196
+ (2 T I). Identity weights reproduce the aspatial indices.
197
+ - **Inference.** `null="labels"` is random allocation: individuals' group labels are
198
+ shuffled while zone sizes and group totals stay fixed (exact multivariate
199
+ hypergeometric draws). `null="zones"` shuffles whole zone compositions among
200
+ locations, which tests the spatial arrangement only. p = (1 + #{sim ≥ obs})/(1 + R).
201
+ The bootstrap redraws each zone from a multinomial with its observed shares
202
+ (geometry fixed) and reports percentile intervals.
203
+
204
+ ### Validation
205
+
206
+ The tests check every index against values worked out by hand: complete segregation
207
+ gives 1, an even distribution gives 0, and a four-zone city has exact fractions
208
+ (D = 5/9, ACO = 14/15, RCO = 77/92, ACE = 79/120, ...). Thirteen two-group indices
209
+ and the multigroup D and H also match PySAL `segregation` 2.6.0 to 1e-9 on an
210
+ irregular eight-zone layout. Wong's D(w) matches PySAL when given PySAL's boundary
211
+ matrix. On polygons that meet at T-junctions, PySAL's vertex-based contiguity misses
212
+ some neighbours, while `segstats` measures collinear edge overlap directly.
213
+
214
+ ### References
215
+
216
+ - Massey, D. S. & Denton, N. A. (1988). The dimensions of residential segregation. *Social Forces* 67(2), 281–315. https://doi.org/10.2307/2579183
217
+ - Bell, W. (1954). A probability model for the measurement of ecological segregation. *Social Forces* 32(4), 357–364.
218
+ - Duncan, O. D. & Duncan, B. (1955). A methodological analysis of segregation indexes. *American Sociological Review* 20(2), 210–217.
219
+ - Atkinson, A. B. (1970). On the measurement of inequality. *Journal of Economic Theory* 2(3), 244–263.
220
+ - White, M. J. (1983). The measurement of spatial segregation. *American Journal of Sociology* 88(5), 1008–1018.
221
+ - White, M. J. (1986). Segregation and diversity measures in population distribution. *Population Index* 52(2), 198–221.
222
+ - Morrill, R. L. (1991). On the measure of geographic segregation. *Geography Research Forum* 11, 25–36.
223
+ - Wong, D. W. S. (1993). Spatial indices of segregation. *Urban Studies* 30(3), 559–572.
224
+ - Reardon, S. F. & Firebaugh, G. (2002). Measures of multigroup segregation. *Sociological Methodology* 32, 33–67.
225
+ - Reardon, S. F. & O'Sullivan, D. (2004). Measures of spatial segregation. *Sociological Methodology* 34, 121–162.
226
+ - Hong, S.-Y., O'Sullivan, D. & Sadahiro, Y. (2014). Implementing spatial segregation measures in R. *PLoS ONE* 9(11), e113767.
227
+ - Allen, R., Burgess, S., Davidson, R. & Windmeijer, F. (2015). More reliable inference for the dissimilarity index of segregation. *The Econometrics Journal* 18(1), 40–66.
228
+ - Cortese, C. F., Falk, R. F. & Cohen, J. K. (1976). Further considerations on the methodological analysis of segregation indices. *American Sociological Review* 41(4), 630–637.
229
+
230
+ ---
231
+
232
+ ## Limitations
233
+
234
+ - **Distance units.** The `exp(-d)` proximity behind SP, RCL and ACL depends on
235
+ the distance unit. `decay_scale` defaults to 1000 (km for metric coordinates).
236
+ Massey & Denton do not fix a unit, so report the scale you use.
237
+ - **Geometry.** Polygons are planar. Lon/lat GeoJSON is projected with a local
238
+ equirectangular approximation, which is fine at city scale. Reproject large regions
239
+ beforehand. Contiguity needs clean topology: gaps wider than the tolerance (1e-9 of
240
+ the extent) break adjacency. Distances are measured between area-weighted centroids.
241
+ - **Ties.** ACO and RCO sort zones by area. When several zones have exactly the same
242
+ area, the core set follows input order (as in PySAL, whose tie order is arbitrary).
243
+ - **Small-unit bias.** D, H and the other indices are biased upward in small zones.
244
+ Use the permutation null mean (`res.excess`) to judge it. The percentile bootstrap
245
+ does not correct this bias.
246
+ - **Integer counts.** The permutation tests and the multinomial bootstrap need
247
+ whole-number counts. Weighted or estimated (e.g. ACS) counts must be rounded, and
248
+ their sampling error is not modelled.
249
+ - **Not included:** network distances, multiscalar profiles, decomposition,
250
+ comparative (two-city) tests and the distance-decay exposure indices. PySAL
251
+ `segregation` covers these.
252
+ - `D_s` follows Wong (1993) with the full zone perimeter. The R package `seg`
253
+ uses only the shared part of the perimeter, which you can pass explicitly.
254
+
255
+ ---
256
+
257
+ ## Development
258
+
259
+ ```bash
260
+ pip install -e ".[dev,pandas]"
261
+ pytest --cov=segstats
262
+ ruff check . && ruff format --check src tests && mypy src
263
+ ```
264
+
265
+ ## License
266
+
267
+ MIT © 2026 Yusuf Eminoğlu. See [LICENSE](LICENSE). To cite segstats, see
268
+ [CITATION.cff](CITATION.cff).
269
+
270
+ ## Academic citation
271
+
272
+ ```bibtex
273
+ @software{eminoglu_segstats,
274
+ author = {Eminoğlu, Yusuf},
275
+ title = {segstats},
276
+ year = {2026},
277
+ version = {0.1.0},
278
+ url = {https://gitlab.com/geophilo1/segstats_sdk}
279
+ }
280
+ ```
281
+
282
+ See also `CITATION.cff`.
@@ -0,0 +1,244 @@
1
+ # segstats
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/segstats.svg)](https://pypi.org/project/segstats/)
4
+ [![Python versions](https://img.shields.io/pypi/pyversions/segstats.svg)](https://pypi.org/project/segstats/)
5
+ [![pipeline](https://gitlab.com/geophilo1/segstats_sdk/badges/main/pipeline.svg)](https://gitlab.com/geophilo1/segstats_sdk/-/pipelines)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
+
8
+ **Residential segregation and diversity indices for urban analysis.**
9
+
10
+ `segstats` computes the classic segregation indices from a table of population
11
+ counts by zone (census tracts, neighbourhoods, *mahalle*) and, when zone polygons are
12
+ available, their spatial counterparts. It covers the five dimensions of Massey &
13
+ Denton (1988), multigroup indices, spatially adjusted measures, local indices per
14
+ zone, and Monte Carlo inference. It needs only NumPy. Geometry comes from plain
15
+ GeoJSON; shapely and GDAL are not required.
16
+
17
+ ```bash
18
+ pip install segstats # numpy only
19
+ pip install "segstats[pandas]" # + DataFrame input
20
+ ```
21
+
22
+ Requires Python 3.9 or newer.
23
+
24
+ > **Looking for more?** The PySAL
25
+ > [`segregation`](https://github.com/pysal/segregation) package is a fuller
26
+ > alternative. It adds many more indices, decomposition, network distances,
27
+ > multiscalar profiles and comparative inference, but needs the full geospatial
28
+ > stack. `segstats` is a small, dependency-light toolkit whose numbers are checked
29
+ > against hand calculations and PySAL (see *Validation*).
30
+
31
+ ---
32
+
33
+ ## Quickstart
34
+
35
+ ```python
36
+ import segstats as ss
37
+
38
+ # counts only: CSV with columns tract, black, total
39
+ data = ss.read_csv("tracts.csv", id_column="tract", groups=["black"], total_column="total")
40
+ x, t = data.two_group("black") # group counts and zone totals
41
+ ss.dissimilarity(x, t) # 0 = even, 1 = complete segregation
42
+ ss.profile(data, group="black") # every aspatial index at once
43
+
44
+ # counts + polygons (GeoJSON in lon/lat is projected to metres on the fly)
45
+ data = ss.read_geojson("tracts.geojson", id_property="GEOID", counts=data)
46
+ ss.profile(data, group="black", center=(28.98, 41.01), bandwidth=1000)
47
+
48
+ # Is D higher than random allocation would give?
49
+ res = ss.permutation_test(data, "D", group="black", n_permutations=999, seed=1)
50
+ res.statistic, res.null_mean, res.p_value
51
+
52
+ # 95% bootstrap CI of the spatial information theory index (1 km Gaussian kernel)
53
+ ci = ss.bootstrap_ci(data, "SH", bandwidth=1000, seed=1)
54
+ ci.ci_low, ci.ci_high
55
+
56
+ # local measures
57
+ ss.location_quotient(data.counts) # zones x groups
58
+ ss.local_entropy(data.counts) # normalised diversity per zone
59
+ ```
60
+
61
+ See [`examples/quickstart.py`](examples/quickstart.py) for a runnable synthetic city.
62
+
63
+ ### Command line
64
+
65
+ ```bash
66
+ segstats list # all indices and their inputs
67
+ segstats indices tracts.csv --id tract --groups black --total total --group black
68
+ segstats indices tracts.csv --id tract --groups black --total total \
69
+ --geojson tracts.geojson --geo-id GEOID --group black \
70
+ --center 28.98,41.01 --bandwidth 1000 --permutations 999 --bootstrap 999 --seed 1
71
+ segstats local tracts.geojson --id GEOID --groups black,white,asian -o local.csv
72
+ ```
73
+
74
+ `--format table|json|csv` controls the output. A GeoJSON file can hold the counts in
75
+ its feature properties, or a CSV table can be joined to polygons with `--geojson`.
76
+
77
+ ---
78
+
79
+ ## Indices
80
+
81
+ Two-group indices compare a group of interest (*x*) with everyone else
82
+ (*y = t - x*). Multigroup indices use all count columns.
83
+
84
+ | Name | Index | Dimension | Inputs | Source |
85
+ |---|---|---|---|---|
86
+ | `D` | Dissimilarity | evenness | counts | Duncan & Duncan 1955 |
87
+ | `G` | Gini | evenness | counts | Massey & Denton 1988 |
88
+ | `H` | Entropy / Theil H | evenness | counts | Theil & Finizza 1971 |
89
+ | `A` | Atkinson (shape *b*) | evenness | counts | Atkinson 1970 |
90
+ | `MD`, `MH` | Multigroup D and H | evenness | counts | Reardon & Firebaugh 2002 |
91
+ | `xPx` | Isolation | exposure | counts | Bell 1954; M&D 1988 |
92
+ | `xPy` | Interaction | exposure | counts | M&D 1988 |
93
+ | `V` | Correlation ratio (eta squared) | exposure | counts | Bell 1954; White 1986 |
94
+ | `DEL` | Delta | concentration | area | Duncan, Cuzzort & Duncan 1961 |
95
+ | `ACO`, `RCO` | Absolute / relative concentration | concentration | area | M&D 1988 |
96
+ | `ACE`, `RCE` | Absolute / relative centralization | centralization | centre | M&D 1988 |
97
+ | `ACL`, `RCL` | Absolute / relative clustering | clustering | distances | M&D 1988 |
98
+ | `SP` | Spatial proximity | clustering | distances | White 1983 |
99
+ | `D_adj` | Adjusted D (contiguity) | spatial evenness | contiguity | Morrill 1991 |
100
+ | `D_w`, `D_s` | Boundary-weighted and shape-adjusted D | spatial evenness | boundaries | Wong 1993 |
101
+ | `SD`, `SH` | Spatial dissimilarity and information theory index | spatial evenness | neighbourhood | Reardon & O'Sullivan 2004 |
102
+
103
+ Local indices: `location_quotient`, `local_entropy`, `local_spatial_entropy`
104
+ (entropy of each zone's kernel-weighted local environment), and
105
+ `local_dissimilarity_contribution`.
106
+
107
+ ### API overview
108
+
109
+ | Area | Functions |
110
+ |---|---|
111
+ | Plain arrays | `dissimilarity(x, t)`, `gini`, `entropy`, `atkinson(x, t, b)`, `isolation`, `interaction`, `correlation_ratio`, `multigroup_dissimilarity(counts)`, `multigroup_entropy`, `exposure_matrix`, `delta(x, t, area)`, `absolute_concentration`, `relative_concentration`, `absolute_centralization(x, area, dist)`, `relative_centralization(x, t, dist)`, `absolute_clustering(x, t, c)`, `relative_clustering`, `spatial_proximity`, `proximity_matrix(d, area, scale)`, `morrill_dissimilarity(x, t, C)`, `wong_dissimilarity_w(x, t, B)`, `wong_dissimilarity_s(x, t, B, perimeter, area)`, `local_environment(counts, W)`, `spatial_dissimilarity(counts, W)`, `spatial_entropy(counts, W)` |
112
+ | Data | `ZoneData`, `read_csv`, `read_geojson`, `from_dataframe`, `ZoneGeometry` (`area`, `perimeter`, `centroids`, `contiguity("queen"/"rook")`, `boundary_lengths()`, `distances()`) |
113
+ | Weights | `distance_matrix`, `kernel_weights(d, bandwidth, kernel)`, `with_self`, `row_standardize` |
114
+ | By name | `compute(data, "D", group)`, `profile(data, group)`, `statistic(...)`, `INDICES`, `IndexOptions` |
115
+ | Inference | `permutation_test(data, index, group, null="labels"/"zones")`, `bootstrap_ci(data, index, group, method="multinomial"/"zones")`, `random_allocation` |
116
+
117
+ `IndexOptions` holds the options shared by the registry: Atkinson `b`, `center` or
118
+ `center_distance`, `decay_scale` (distance unit of `exp(-d)`, default 1000 for km
119
+ when coordinates are metres), `clustering` (`"proximity"` or `"contiguity"` matrix for
120
+ ACL), `bandwidth` and `kernel` (`gaussian`, `exponential`, `biweight`, `box`) for the
121
+ Reardon & O'Sullivan neighbourhood (default: the zone plus its contiguous
122
+ neighbours), and `contiguity` (`queen`/`rook`).
123
+
124
+ ---
125
+
126
+ ## Method and sources
127
+
128
+ Notation: *x_i*, *y_i*, *t_i* are the group, rest-of-population and total counts of
129
+ zone *i*; capitals are city totals; *p_i = x_i / t_i*, *P = X / T*; *a_i* is the zone
130
+ area.
131
+
132
+ - **D** = ½ Σ |x_i/X − y_i/Y|. **G** = ΣΣ t_i t_j |p_i − p_j| / (2T²P(1−P)), computed
133
+ in O(n log n) from sorted proportions. **H** = Σ t_i (E − E_i) / (E T) with Shannon
134
+ entropies E, E_i (natural log). **A** = 1 − P/(1−P) · |Σ (1−p_i)^(1−b) p_i^b t_i /
135
+ (P T)|^(1/(1−b)). (Massey & Denton 1988, eqs. 1–4.)
136
+ - **xPx** = Σ (x_i/X)(x_i/t_i), **xPy** = Σ (x_i/X)(y_i/t_i), **V** = (xPx − P)/(1 − P).
137
+ - **Multigroup** D = Σ_m Σ_i t_i |π_im − π_m| / (2 T I), with I = Σ π_m(1 − π_m), and
138
+ Theil's H (Reardon & Firebaugh 2002).
139
+ - **DEL** = ½ Σ |x_i/X − a_i/A|. **ACO** and **RCO** compare the group's
140
+ area-weighted mean zone size with that of the n₁ smallest and the n₂ largest zones
141
+ holding X people (Massey & Denton 1988, eqs. 10–11; same convention for n₁ and n₂
142
+ as PySAL).
143
+ - **ACE** = Σ X_{i−1}A_i − Σ X_i A_{i−1} and **RCE** = Σ X_{i−1}Y_i − Σ X_i Y_{i−1} over
144
+ zones sorted by distance to the centre (cumulative shares). Zones at equal distance
145
+ form one step, so the result does not depend on input order.
146
+ - **SP** = (X Pxx + Y Pyy)/(T Ptt), **RCL** = Pxx/Pyy − 1, with Pxx = ΣΣ x_i x_j c_ij /
147
+ X² and c_ij = exp(−d_ij / scale), d_ii = (0.6 a_i)^0.5 (Massey & Denton 1988; White
148
+ 1983). **ACL** follows Massey & Denton eq. 14 with the chosen c matrix.
149
+ - **D(adj)** = D − ΣΣ c_ij |p_i − p_j| / ΣΣ c_ij (binary contiguity; Morrill 1991).
150
+ **D(w)** = D − ΣΣ w_ij |p_i − p_j| with w_ij = b_ij / ΣΣ b_kl, the normalised
151
+ shared-boundary length. **D(s)** additionally multiplies each term by
152
+ ½(PA_i + PA_j)/max(PA), PA = perimeter/area (Wong 1993). The normalisation follows
153
+ the R package `seg` (Hong, O'Sullivan & Sadahiro 2014). Zones with no population are
154
+ left out of the adjustment term.
155
+ - **Spatial D and H** (Reardon & O'Sullivan 2004): each zone's local environment
156
+ is π̃_im = Σ_j w_ij x_jm / Σ_j w_ij t_j, with kernel weights that include the
157
+ zone itself. Then H̃ = 1 − Σ t_i Ẽ_i / (T E) and D̃ = Σ_m Σ_i t_i |π̃_im − π_m| /
158
+ (2 T I). Identity weights reproduce the aspatial indices.
159
+ - **Inference.** `null="labels"` is random allocation: individuals' group labels are
160
+ shuffled while zone sizes and group totals stay fixed (exact multivariate
161
+ hypergeometric draws). `null="zones"` shuffles whole zone compositions among
162
+ locations, which tests the spatial arrangement only. p = (1 + #{sim ≥ obs})/(1 + R).
163
+ The bootstrap redraws each zone from a multinomial with its observed shares
164
+ (geometry fixed) and reports percentile intervals.
165
+
166
+ ### Validation
167
+
168
+ The tests check every index against values worked out by hand: complete segregation
169
+ gives 1, an even distribution gives 0, and a four-zone city has exact fractions
170
+ (D = 5/9, ACO = 14/15, RCO = 77/92, ACE = 79/120, ...). Thirteen two-group indices
171
+ and the multigroup D and H also match PySAL `segregation` 2.6.0 to 1e-9 on an
172
+ irregular eight-zone layout. Wong's D(w) matches PySAL when given PySAL's boundary
173
+ matrix. On polygons that meet at T-junctions, PySAL's vertex-based contiguity misses
174
+ some neighbours, while `segstats` measures collinear edge overlap directly.
175
+
176
+ ### References
177
+
178
+ - Massey, D. S. & Denton, N. A. (1988). The dimensions of residential segregation. *Social Forces* 67(2), 281–315. https://doi.org/10.2307/2579183
179
+ - Bell, W. (1954). A probability model for the measurement of ecological segregation. *Social Forces* 32(4), 357–364.
180
+ - Duncan, O. D. & Duncan, B. (1955). A methodological analysis of segregation indexes. *American Sociological Review* 20(2), 210–217.
181
+ - Atkinson, A. B. (1970). On the measurement of inequality. *Journal of Economic Theory* 2(3), 244–263.
182
+ - White, M. J. (1983). The measurement of spatial segregation. *American Journal of Sociology* 88(5), 1008–1018.
183
+ - White, M. J. (1986). Segregation and diversity measures in population distribution. *Population Index* 52(2), 198–221.
184
+ - Morrill, R. L. (1991). On the measure of geographic segregation. *Geography Research Forum* 11, 25–36.
185
+ - Wong, D. W. S. (1993). Spatial indices of segregation. *Urban Studies* 30(3), 559–572.
186
+ - Reardon, S. F. & Firebaugh, G. (2002). Measures of multigroup segregation. *Sociological Methodology* 32, 33–67.
187
+ - Reardon, S. F. & O'Sullivan, D. (2004). Measures of spatial segregation. *Sociological Methodology* 34, 121–162.
188
+ - Hong, S.-Y., O'Sullivan, D. & Sadahiro, Y. (2014). Implementing spatial segregation measures in R. *PLoS ONE* 9(11), e113767.
189
+ - Allen, R., Burgess, S., Davidson, R. & Windmeijer, F. (2015). More reliable inference for the dissimilarity index of segregation. *The Econometrics Journal* 18(1), 40–66.
190
+ - Cortese, C. F., Falk, R. F. & Cohen, J. K. (1976). Further considerations on the methodological analysis of segregation indices. *American Sociological Review* 41(4), 630–637.
191
+
192
+ ---
193
+
194
+ ## Limitations
195
+
196
+ - **Distance units.** The `exp(-d)` proximity behind SP, RCL and ACL depends on
197
+ the distance unit. `decay_scale` defaults to 1000 (km for metric coordinates).
198
+ Massey & Denton do not fix a unit, so report the scale you use.
199
+ - **Geometry.** Polygons are planar. Lon/lat GeoJSON is projected with a local
200
+ equirectangular approximation, which is fine at city scale. Reproject large regions
201
+ beforehand. Contiguity needs clean topology: gaps wider than the tolerance (1e-9 of
202
+ the extent) break adjacency. Distances are measured between area-weighted centroids.
203
+ - **Ties.** ACO and RCO sort zones by area. When several zones have exactly the same
204
+ area, the core set follows input order (as in PySAL, whose tie order is arbitrary).
205
+ - **Small-unit bias.** D, H and the other indices are biased upward in small zones.
206
+ Use the permutation null mean (`res.excess`) to judge it. The percentile bootstrap
207
+ does not correct this bias.
208
+ - **Integer counts.** The permutation tests and the multinomial bootstrap need
209
+ whole-number counts. Weighted or estimated (e.g. ACS) counts must be rounded, and
210
+ their sampling error is not modelled.
211
+ - **Not included:** network distances, multiscalar profiles, decomposition,
212
+ comparative (two-city) tests and the distance-decay exposure indices. PySAL
213
+ `segregation` covers these.
214
+ - `D_s` follows Wong (1993) with the full zone perimeter. The R package `seg`
215
+ uses only the shared part of the perimeter, which you can pass explicitly.
216
+
217
+ ---
218
+
219
+ ## Development
220
+
221
+ ```bash
222
+ pip install -e ".[dev,pandas]"
223
+ pytest --cov=segstats
224
+ ruff check . && ruff format --check src tests && mypy src
225
+ ```
226
+
227
+ ## License
228
+
229
+ MIT © 2026 Yusuf Eminoğlu. See [LICENSE](LICENSE). To cite segstats, see
230
+ [CITATION.cff](CITATION.cff).
231
+
232
+ ## Academic citation
233
+
234
+ ```bibtex
235
+ @software{eminoglu_segstats,
236
+ author = {Eminoğlu, Yusuf},
237
+ title = {segstats},
238
+ year = {2026},
239
+ version = {0.1.0},
240
+ url = {https://gitlab.com/geophilo1/segstats_sdk}
241
+ }
242
+ ```
243
+
244
+ See also `CITATION.cff`.