gla-data 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.
@@ -0,0 +1,13 @@
1
+ # Changes here will be overwritten by Copier
2
+ _commit: v0.5.0
3
+ _src_path: gh:Greater-London-Authority/ds-project-template
4
+ author_email: conor.dempsey@london.gov.uk
5
+ author_name: Conor Dempsey
6
+ description: Shared tools for loading, transforming, and visualising the GLA datasets
7
+ used in City Data analysis.
8
+ has_cli: false
9
+ import_name: gla_data
10
+ package_name: gla-data
11
+ platforms: '["osx-arm64", "linux-64", "win-64"]'
12
+ project_type: package
13
+ python_version: '3.12'
@@ -0,0 +1,11 @@
1
+ # Copy this file to .env and fill in values. Never commit .env.
2
+ # Settings are loaded from this file automatically by config.py.
3
+
4
+ # AWS (if used)
5
+ # AWS_DEFAULT_REGION=eu-west-2
6
+ # AWS_ACCESS_KEY_ID=
7
+ # AWS_SECRET_ACCESS_KEY=
8
+
9
+ # Application settings — override here for local development if needed.
10
+ # {{ import_name | upper }}_DATA_DIR=data
11
+ # {{ import_name | upper }}_OUTPUT_DIR=outputs
@@ -0,0 +1,35 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - uses: prefix-dev/setup-pixi@v0.8.3
15
+ with:
16
+ pixi-version: latest
17
+ - run: pixi run test
18
+
19
+ lint:
20
+ runs-on: ubuntu-latest
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+ - uses: prefix-dev/setup-pixi@v0.8.3
24
+ with:
25
+ pixi-version: latest
26
+ - run: pixi run lint
27
+
28
+ typecheck:
29
+ runs-on: ubuntu-latest
30
+ steps:
31
+ - uses: actions/checkout@v4
32
+ - uses: prefix-dev/setup-pixi@v0.8.3
33
+ with:
34
+ pixi-version: latest
35
+ - run: pixi run typecheck
@@ -0,0 +1,33 @@
1
+ name: Publish
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ jobs:
9
+ build:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: prefix-dev/setup-pixi@v0.8.3
14
+ with:
15
+ pixi-version: latest
16
+ - run: pixi run build
17
+ - uses: actions/upload-artifact@v4
18
+ with:
19
+ name: dist
20
+ path: dist/
21
+
22
+ publish:
23
+ needs: build
24
+ runs-on: ubuntu-latest
25
+ environment: pypi
26
+ permissions:
27
+ id-token: write
28
+ steps:
29
+ - uses: actions/download-artifact@v4
30
+ with:
31
+ name: dist
32
+ path: dist/
33
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,34 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .eggs/
8
+
9
+ # Pixi
10
+ .pixi/
11
+
12
+ # Testing
13
+ .pytest_cache/
14
+ .coverage
15
+ htmlcov/
16
+
17
+ # Type checking
18
+ .mypy_cache/
19
+
20
+ # Editors
21
+ .vscode/
22
+ .idea/
23
+ *.swp
24
+
25
+ # Secrets
26
+ .env
27
+
28
+ # Data — immutable source data and generated outputs are not committed
29
+ data/raw/
30
+ outputs/
31
+
32
+ # OS
33
+ .DS_Store
34
+ Thumbs.db
@@ -0,0 +1,283 @@
1
+ # gla-data design
2
+
3
+ ## Purpose
4
+
5
+ `gla-data` is a shared Python package for the GLA City Intelligence Data Science team. It provides clean, consistent access to the GLA-relevant datasets used across City Data analysis projects.
6
+
7
+ The primary goal is to reduce the cost of getting started with a new dataset. A user who knows a dataset exists but has not yet used it — because the API is unfamiliar, credentials are unclear, or the raw data requires significant cleaning — should be able to load analysis-ready data in a few lines of code.
8
+
9
+ ## Who this is for
10
+
11
+ Data scientists on the City Intelligence team working on GLA analysis projects. Users are assumed to have some familiarity with the datasets, but `gla-data` should make it easy to discover what is available and how to use it, including:
12
+
13
+ - What credentials or sign-ups are required to access a dataset
14
+ - What the default filters are and why
15
+ - What parameters are available to adjust those defaults
16
+
17
+ ## What this package does
18
+
19
+ - Loads GLA-relevant datasets from public sources and (in future) from the GLA S3 data lake
20
+ - Returns data filtered to sensible defaults (typically London, latest available year)
21
+ - Provides standard geography transformations: aggregation, disaggregation, and year crosswalks
22
+ - Documents required credentials and setup steps for each data source in the README; functions raise clear errors with a pointer to the README if credentials are missing
23
+
24
+ ## What this package does not do
25
+
26
+ - Project-specific cleaning, transformation, or modelling — these belong in project code
27
+ - Cross-dataset merging — users join datasets themselves; `gla-data` provides the building blocks
28
+ - Exploratory analysis or visualisation — may be revisited once clear patterns emerge across projects
29
+
30
+ ---
31
+
32
+ ## Design principles
33
+
34
+ **Defaults should be sensible and explicit.** Every parameter with a default should have a reason for that default, and the docstring should state it. A user should be able to call any function with no arguments and get something useful.
35
+
36
+ **Return types are consistent.** All load functions return a `pd.DataFrame`. Functions that return geographic data return a `gpd.GeoDataFrame`. Column naming follows ONS conventions where they exist (e.g. `LSOA21CD`, `LAD22NM`).
37
+
38
+ **Functions are stateless.** Call a function, get data back. No objects to manage, no state to track between calls.
39
+
40
+ **Private functions do the work, public functions are the interface.** HTTP calls, API authentication, raw data parsing, and column renaming all happen in private functions prefixed with `_`. Public functions are thin and readable.
41
+
42
+ **Source switching is per-function.** Each function accepts a `source` parameter defaulting to `"public"`. As datasets become available on the GLA S3 data lake, `source="s3"` is added to that function. Functions that do not yet support S3 raise a clear error if `source="s3"` is passed.
43
+
44
+ ---
45
+
46
+ ## Data access tiers
47
+
48
+ Datasets differ in what setup is required. Each function's docstring and the catalogue state which tier it belongs to. Setup instructions for datasets requiring credentials are in the README. Functions that require credentials raise a clear, descriptive error if the relevant environment variable is missing — for example:
49
+
50
+ ```
51
+ EnvironmentError: EPC_API_KEY not set. This dataset requires a free DLUHC account.
52
+ See the README for setup instructions.
53
+ ```
54
+
55
+ | Tier | Description | Examples |
56
+ |---|---|---|
57
+ | `public` | No credentials required. URLs are embedded in the package. | ONS boundaries, census data via Nomis |
58
+ | `registered` | Requires a free account or API key. Set via environment variable. | EPC (DLUHC portal), Nomis bulk API key |
59
+ | `licensed` | Requires a government or commercial licence. | OS AddressBase |
60
+ | `s3` | Requires access to the GLA internal data lake (provisioned by GLA engineering). | TBD |
61
+
62
+ ---
63
+
64
+ ## API conventions
65
+
66
+ ### Naming
67
+
68
+ - Data loading functions: `load_<dataset>` or `load_boundaries(geography=...)`
69
+ - Geography transformation functions: `aggregate(...)`, `crosswalk(...)`
70
+ - Discovery functions: `catalogue()`
71
+
72
+ Parameters describe what the user wants, not how the code fetches it.
73
+
74
+ ### Standard parameters
75
+
76
+ | Parameter | Type | Notes |
77
+ |---|---|---|
78
+ | `geography` | `str` | Geographic unit: `"oa"`, `"lsoa"`, `"msoa"`, `"ward"`, `"borough"`, `"london"` |
79
+ | `year` | `int` | Reference year. Valid values are documented per function |
80
+ | `source` | `Literal["public", "s3"]` | Data source. Defaults to `"public"` |
81
+
82
+ ### Return types
83
+
84
+ | Function type | Return type | Notes |
85
+ |---|---|---|
86
+ | Tabular data | `pd.DataFrame` | Index is always a plain integer range index |
87
+ | Geographic data | `gpd.GeoDataFrame` | CRS is always British National Grid (EPSG:27700) |
88
+
89
+ ### Column naming
90
+
91
+ - Geography codes follow ONS conventions: `LSOA21CD`, `LAD22NM`, `OA21CD` etc.
92
+ - The year in a geography code column name reflects the geography vintage, not the data year
93
+ - All column names are uppercase with underscores
94
+
95
+ ---
96
+
97
+ ## Public API
98
+
99
+ ### Discovery
100
+
101
+ ```python
102
+ gla_data.catalogue()
103
+ ```
104
+
105
+ Returns a `pd.DataFrame` listing all available datasets with columns: `dataset`, `description`, `access_tier`, `status`, `source_supported`. For datasets with `access_tier` other than `public`, the catalogue notes that setup is required — see the README for instructions.
106
+
107
+ ---
108
+
109
+ ### Boundaries
110
+
111
+ ```python
112
+ gdf = gla_data.load_boundaries(geography="lsoa", year=2021)
113
+ ```
114
+
115
+ Returns boundary polygons for London at the specified geography and year.
116
+
117
+ **Parameters:**
118
+ - `geography`: `"oa"`, `"lsoa"`, `"msoa"`, `"ward"`, `"borough"`, `"london"`
119
+ - `year`: valid values depend on geography (e.g. LSOAs: `2011`, `2021`; borough boundaries do not have a year)
120
+ - `source`: `"public"` only for now
121
+
122
+ **Returns:** `gpd.GeoDataFrame` with columns:
123
+ - Geography code and name (e.g. `LSOA21CD`, `LSOA21NM`)
124
+ - `geometry` — polygon in EPSG:27700
125
+
126
+ **Access tier:** `public`
127
+
128
+ ---
129
+
130
+ ### Geography lookup
131
+
132
+ ```python
133
+ df = gla_data.load_geography_lookup(year=2021, smallest_geography="lsoa")
134
+ ```
135
+
136
+ Returns the ONS lookup table mapping London LSOAs (or OAs) to higher geographies (MSOA, borough).
137
+
138
+ **Parameters:**
139
+ - `year`: `2011` or `2021`
140
+ - `smallest_geography`: `"lsoa"` (default, one row per LSOA) or `"oa"` (one row per OA, includes OA code column)
141
+ - `source`: `"public"` only for now
142
+ - `cache`: if `True` (default), result is cached to `~/.cache/gla_data/`
143
+
144
+ **Returns:** `pd.DataFrame`. Columns for 2021 (lsoa grain):
145
+ `LSOA21CD`, `LSOA21NM`, `MSOA21CD`, `MSOA21NM`, `LAD22CD`, `LAD22NM`.
146
+ For 2011: `LSOA11CD`, `LSOA11NM`, `MSOA11CD`, `MSOA11NM`, `LAD20CD`, `LAD20NM`.
147
+ Note: the 2011 table uses 2020 LAD boundaries — this is what ONS provides at this grain.
148
+
149
+ **Access tier:** `public`
150
+
151
+ **Notes:**
152
+ - Source tables are at OA grain. For `smallest_geography="lsoa"`, deduplication is applied.
153
+ - Fetching is slow (~17s). A `UserWarning` is raised on first call; result is cached to disk by default.
154
+
155
+ ---
156
+
157
+ ### Census dwellings
158
+
159
+ ```python
160
+ df = gla_data.load_census_dwellings(year=2021, geography="lsoa")
161
+ ```
162
+
163
+ Returns dwelling counts for London at the specified geography and census year.
164
+
165
+ **Parameters:**
166
+ - `year`: `2011` or `2021`
167
+ - `geography`: `"oa"`, `"lsoa"`, `"msoa"`, `"borough"`, `"london"`
168
+ - `source`: `"public"` only for now
169
+
170
+ **Returns:** `pd.DataFrame` with a geography code column (e.g. `LSOA21CD`) and:
171
+ - `dwellings_total`
172
+ - Dwelling type breakdown: `dwellings_detached`, `dwellings_semi_detached`, `dwellings_terraced`, `dwellings_flat`, `dwellings_other`
173
+ - Household space type breakdown: `spaces_total`, `spaces_occupied`, `spaces_vacant`
174
+
175
+ **Access tier:** `public`
176
+
177
+ **Notes:**
178
+ - 2011 data uses 2011 LSOA boundaries; 2021 data uses 2021 LSOA boundaries. Use `crosswalk()` if you need both years on consistent boundaries.
179
+ - When `geography` is above LSOA, data is aggregated via the standard ONS lookup.
180
+
181
+ ---
182
+
183
+ ### EPC
184
+
185
+ ```python
186
+ df = gla_data.load_epc(geography="lsoa", year=2023)
187
+ ```
188
+
189
+ Returns Energy Performance Certificate data for London.
190
+
191
+ **Access tier:** `registered` — requires a free account at the DLUHC EPC portal. Run `gla_data.setup_instructions(dataset="epc")` for details.
192
+
193
+ *Further design TBD once initial datasets are implemented.*
194
+
195
+ ---
196
+
197
+ ### Geography transformation
198
+
199
+ #### Aggregation
200
+
201
+ ```python
202
+ df_msoa = gla_data.aggregate(
203
+ df,
204
+ from_geography="lsoa",
205
+ to_geography="msoa",
206
+ value_cols=["dwellings_total", "dwellings_vacant"],
207
+ method="sum",
208
+ year=2021,
209
+ )
210
+ ```
211
+
212
+ Aggregates a DataFrame from a lower to a higher geography using the standard ONS lookup. The `year` parameter specifies which vintage of lookup to use.
213
+
214
+ **Parameters:**
215
+ - `df`: input DataFrame containing a geography code column (auto-detected by name)
216
+ - `from_geography`: source geography
217
+ - `to_geography`: target geography (must be higher than `from_geography`)
218
+ - `value_cols`: columns to aggregate
219
+ - `method`: `"sum"` or `"mean"`
220
+ - `year`: lookup vintage
221
+
222
+ #### Crosswalk (same geography, different year)
223
+
224
+ ```python
225
+ df_2021 = gla_data.crosswalk(
226
+ df,
227
+ geography="lsoa",
228
+ from_year=2011,
229
+ to_year=2021,
230
+ value_cols=["dwellings_total"],
231
+ method="proportional",
232
+ )
233
+ ```
234
+
235
+ Converts a DataFrame from one year's geography boundaries to another using the ONS exact-fit or best-fit lookup.
236
+
237
+ **Parameters:**
238
+ - `df`: input DataFrame
239
+ - `geography`: the geographic unit
240
+ - `from_year`, `to_year`: boundary vintages
241
+ - `value_cols`: numeric columns to convert
242
+ - `method`: `"proportional"` (split values by area overlap) or `"exact_fit"` (for cases where boundaries nest cleanly)
243
+
244
+ **Notes:**
245
+ - Disaggregation (higher to lower geography) is not supported in the first version. The `aggregate` function is one-directional.
246
+ - Disaggregation requires assumptions about how values are distributed within a higher geography. If this is needed, it should be designed explicitly with a stated method (e.g. uniform distribution, population-weighted).
247
+
248
+ ---
249
+
250
+ ## Package structure
251
+
252
+ ```
253
+ src/gla_data/
254
+ ├── __init__.py ← public API: re-exports all public functions
255
+ ├── py.typed
256
+ ├── config.py ← environment variable handling (S3 config etc.)
257
+ ├── _arcgis.py ← private: generic ArcGIS FeatureServer HTTP/pagination
258
+ ├── _ons.py ← private: ONS service constants and fetch functions
259
+ ├── _ukds.py ← private: UKDS dataset registry and static file downloads
260
+ ├── _cache.py ← private: shared disk cache (~/.cache/gla_data/)
261
+ ├── geography.py ← load_boundaries(), load_geography_lookup()
262
+ ├── transforms.py ← crosswalk() [aggregate() to be added]
263
+ ├── catalogue.py ← catalogue() [not yet implemented]
264
+ ├── census.py ← load_census_dwellings()
265
+ ├── epc.py ← load_epc() [not yet implemented]
266
+ ```
267
+
268
+ ### Notes on the private layer
269
+
270
+ `_arcgis.py` contains generic ArcGIS FeatureServer pagination logic (no ONS-specific
271
+ knowledge). `_ons.py` holds ONS service constants and the fetch functions that use them.
272
+ When new data sources are added (e.g. Nomis for census, DLUHC for EPC), they get their
273
+ own private module and introduce new HTTP helpers only if needed.
274
+
275
+ ---
276
+
277
+ ## Open questions
278
+
279
+ - `aggregate` and `crosswalk` are generic functions (not per-geography-pair). They auto-detect the geography code column by name — open question: what happens if detection is ambiguous?
280
+ - What is the right disaggregation method for numeric quantities (MSOA → LSOA)? Uniform? Population-weighted? Defer until there is a concrete use case.
281
+ - Should `load_boundaries` accept `year=None` for geographies that do not have vintages (e.g. borough boundaries)?
282
+ - Where does Land Registry data fit — access tier, likely `public` (price paid) or `registered` (title register)?
283
+ - OS AddressBase: confirm whether the GLA licence covers use in this package or whether each team member needs their own licence.
@@ -0,0 +1,43 @@
1
+ Metadata-Version: 2.4
2
+ Name: gla-data
3
+ Version: 0.1.0
4
+ Summary: Shared tools for loading, transforming, and visualising the GLA datasets used in City Data analysis.
5
+ Author-email: Conor Dempsey <conor.dempsey@london.gov.uk>
6
+ License: MIT
7
+ Requires-Python: >=3.12
8
+ Requires-Dist: geopandas>=1.0
9
+ Requires-Dist: pandas>=2.2
10
+ Requires-Dist: pandera>=0.32.1
11
+ Requires-Dist: pyarrow>=24.0.0
12
+ Requires-Dist: pydantic-settings
13
+ Requires-Dist: requests>=2.32
14
+ Requires-Dist: typer>=0.26.8
15
+ Description-Content-Type: text/markdown
16
+
17
+ # gla-data
18
+
19
+ Shared tools for loading, transforming, and visualising the GLA datasets used in City Data analysis.
20
+
21
+ ## Installation
22
+
23
+ Add `gla-data` as a PyPI dependency in your `pixi.toml`:
24
+
25
+ ```toml
26
+ [pypi-dependencies]
27
+ gla-data = ">=0.1"
28
+ ```
29
+
30
+ Then run `pixi install`. Pixi pulls geospatial dependencies (geopandas, pyproj, etc.) from conda-forge, which provides reliable pre-built binaries across all platforms and Python versions.
31
+
32
+ ## Development
33
+
34
+ ```bash
35
+ git clone <repo-url>
36
+ cd gla-data
37
+ pixi install
38
+
39
+ pixi run test # run tests
40
+ pixi run lint # check code style
41
+ pixi run format # auto-format
42
+ pixi run typecheck # type checking
43
+ ```
@@ -0,0 +1,27 @@
1
+ # gla-data
2
+
3
+ Shared tools for loading, transforming, and visualising the GLA datasets used in City Data analysis.
4
+
5
+ ## Installation
6
+
7
+ Add `gla-data` as a PyPI dependency in your `pixi.toml`:
8
+
9
+ ```toml
10
+ [pypi-dependencies]
11
+ gla-data = ">=0.1"
12
+ ```
13
+
14
+ Then run `pixi install`. Pixi pulls geospatial dependencies (geopandas, pyproj, etc.) from conda-forge, which provides reliable pre-built binaries across all platforms and Python versions.
15
+
16
+ ## Development
17
+
18
+ ```bash
19
+ git clone <repo-url>
20
+ cd gla-data
21
+ pixi install
22
+
23
+ pixi run test # run tests
24
+ pixi run lint # check code style
25
+ pixi run format # auto-format
26
+ pixi run typecheck # type checking
27
+ ```