Skip to content

Commit da1daf7

Browse files
committed
added geospatial
1 parent be8fe59 commit da1daf7

17 files changed

Lines changed: 269542 additions & 269065 deletions

File tree

docs/make.jl

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@ makedocs(;
88
"Tutorials" => [
99
"Patient-Level Prediction" => [
1010
"Introduction" => "plp-intro.md",
11-
"PLP Workflow" => "plp-workflow.md",
11+
"Workflow" => "plp-workflow.md",
1212
"Package Examples" => "plp-examples.md",
1313
],
14-
"Geospatial Health Informatics" => "geospatial.md",
14+
"Geospatial Health Informatics" => [
15+
"Introduction" => "geospatial-intro.md",
16+
"Workflow" => "geospatial-workflow.md",
17+
],
1518
"MRI Simulation and Analysis" => "mri.md",
1619
],
1720
],

docs/src/geospatial-intro.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Geospatial Health Informatics Using Census Microdata
2+
3+
Geospatial health informatics studies health and social outcomes in relation to place.
4+
It helps answer questions such as: Which regions have stronger educational attainment?
5+
Where are social risk factors concentrated? Which areas should be prioritized for policy action?
6+
7+
For public health research, this matters because many health patterns are not evenly distributed.
8+
Disease burden, healthcare access, education, income, and environmental exposure often vary by region.
9+
A map-based workflow makes those differences easier to see, compare, and communicate.
10+
11+
In this project, we use census microdata together with administrative boundaries to move from
12+
individual records to region-level indicators. The example focuses on educational attainment,
13+
but the same approach can be reused for many public-health-relevant measures.
14+
15+
## Why Census Microdata + Geospatial Data?
16+
17+
- Census microdata provides person-level or household-level variables, which makes it possible to build detailed indicators.
18+
- IPUMS data projects are designed for integrated population research across time and place, with harmonized documentation and metadata.
19+
- Geospatial datasets provide the boundary geometry (for example NUTS regions), which turns tabular indicators into maps.
20+
- When we aggregate microdata by region and join it to boundaries, we can compare population-level trends and detect spatial inequalities.
21+
22+
In practical terms, microdata gives us the "what" and geospatial boundaries give us the "where".
23+
Combining both gives an interpretable view for public health planning and communication.
24+
25+
## IPUMS in This Workflow
26+
27+
IPUMS (Integrated Public Use Microdata Series) provides structured census and survey data with rich metadata.
28+
In this workflow, IPUMS.jl is used to load:
29+
30+
- DDI metadata (XML)
31+
- microdata extract files (DAT)
32+
- NHGIS-style geospatial files for boundaries
33+
34+
This allows a reproducible, script-first pipeline: load metadata, load records, inspect variables,
35+
and then aggregate to region-level outputs for mapping.
36+
37+
## Package Stack Used
38+
39+
- `GeoMakie.jl`
40+
Geographic plotting utilities in the Makie ecosystem. Its `GeoAxis` supports map projections
41+
and coordinate transformations, so data can be displayed in an appropriate destination projection.
42+
43+
- `CairoMakie.jl`
44+
A high-quality Makie backend for SVG/PDF/vector output. This is well-suited for publication-style
45+
static figures and clean map exports.
46+
47+
- `GeoInterfaceMakie.jl`
48+
Adds Makie plotting support for geometries that implement GeoInterface traits.
49+
In practice, this helps geometry objects from geospatial packages work smoothly with Makie plotting calls.
50+
51+
- `GeoDataFrames.jl`
52+
A DataFrame-oriented way to read and handle geospatial vector data in Julia,
53+
inspired by GeoPandas-style workflows.
54+
55+
- `IPUMS.jl`
56+
The package used here to parse DDI metadata and load census and boundary extracts.
57+
In this repository environment, IPUMS is pinned from the JuliaHealth IPUMS.jl source in the workflow Manifest.
58+
59+
- `DataFrames.jl`, `Chain.jl`, `StatsBase.jl`
60+
Support aggregation, labeling, grouping, and summary steps before visualization.
61+
62+
## End-to-End Logic
63+
64+
The workflow is intentionally explicit and reproducible:
65+
66+
1. Acquire census microdata and metadata with IPUMS.jl.
67+
2. Load and clean region boundaries.
68+
3. Assign readable education categories from coded microdata values.
69+
4. Aggregate and normalize counts for fair visual comparison.
70+
5. Join regional summaries to geospatial geometries.
71+
6. Create choropleth maps with clear titles, color schemes, and colorbars.
72+
73+
Each step is documented and implemented as runnable Julia code in the workflow scripts.
74+
75+
## Reproducibility Note
76+
77+
This example follows a reproducible Poland workflow with explicit file paths, clear transformations,
78+
and deterministic plotting steps. If restricted datasets are unavailable, the same structure can be
79+
reused with other compatible IPUMS extracts and boundary files.

docs/src/geospatial-workflow.md

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
# Geospatial Workflow
2+
3+
This workflow demonstrates:
4+
5+
1. Data acquisition with IPUMS.jl
6+
2. Preprocessing and educational category labeling
7+
3. Integration of aggregated census metrics with geospatial geometries
8+
4. Choropleth visualization with colorbar and layout tuning
9+
10+
The scripts in this page mirror the runnable workflow under `src/workflows/geospatial_census/src/`. The goal is to keep the code easy to read and easy to run: load data, summarize it by region, join to geometry, and plot maps.
11+
12+
## Requirement Coverage
13+
14+
This workflow explicitly covers all required tutorial goals:
15+
16+
- Data acquisition: load census microdata and metadata through IPUMS.jl (`parse_ddi`, `load_ipums_extract`) and load geospatial boundaries (`load_ipums_nhgis`).
17+
- Preprocessing: filter and clean geometry rows, convert join keys, map coded education values to readable labels, and aggregate counts.
18+
- Normalization: compute min-max normalized counts across regions before choropleth coloring.
19+
- Integration: merge aggregated census summaries with geospatial geometries using key-based joins.
20+
- Visualization: produce choropleth maps with multiple color schemes, titles, layout tuning, and a final colorbar for interpretability.
21+
- Reproducibility: provide runnable Julia code, fixed script order, and config-driven local file paths.
22+
23+
## 1. Load Required Packages
24+
25+
```julia
26+
using CairoMakie, Chain, CSV, DataFrames, GeoDataFrames, GeoInterfaceMakie, GeoMakie, StatsBase
27+
import IPUMS: load_ipums_extract, load_ipums_nhgis, parse_ddi
28+
```
29+
30+
## 2. Load IPUMS Census Data
31+
32+
This step reads the metadata dictionary (`.xml`) and the fixed-width microdata extract (`.dat`).
33+
Both paths are configured in `config.toml` when you run the script pipeline.
34+
35+
```julia
36+
ddi = parse_ddi("poland_data/ipumsi_00001.xml")
37+
df = load_ipums_extract(ddi, "poland_data/ipumsi_00001.dat")
38+
```
39+
40+
## 3. Metadata Exploration
41+
42+
This section documents data acquisition quality checks. You inspect dataset-level metadata first, then column-level descriptions to verify
43+
definitions before deriving indicators.
44+
45+
```julia
46+
md_df = metadata(df)
47+
for md in keys(md_df)
48+
println("$(md):\n----------------\n\n $(md_df[md])\n\n")
49+
end
50+
```
51+
52+
Then inspect variable descriptions:
53+
54+
```julia
55+
for colname in names(df)
56+
println("$(colname):\n----------------\n")
57+
try
58+
println(" $(colmetadata(df, colname, "description"))\n")
59+
catch
60+
println(" description metadata not available\n")
61+
end
62+
end
63+
```
64+
65+
## 4. Load and Filter Shapefile for Poland
66+
67+
The boundary file is filtered to Poland, rows without geometry are removed, and the regional key
68+
is cast to `Int64` so it can be joined to aggregated census results.
69+
70+
```julia
71+
geodf = load_ipums_nhgis("shapefiles/ENUTS2_2013.shp").geodataframe
72+
filter!(x -> x.CNTRY_NAME == "Poland", geodf)
73+
dropmissing!(geodf, :geometry)
74+
geodf[!, :ENUTS2] = parse.(Int64, geodf[!, :ENUTS2])
75+
```
76+
77+
## 5. Aggregate Educational Attainment by Region
78+
79+
This step maps raw education codes into readable categories (`PRIMARY`, `SECONDARY`, `UNIVERSITY`),
80+
then computes regional counts for each category.
81+
82+
It also prepares data for comparative mapping by creating grouped regional counts that are later normalized.
83+
84+
```julia
85+
using Chain
86+
87+
edu_df = @chain df begin
88+
groupby(_[:, [:ENUTS2_2013, :EDUCPL]], [:ENUTS2_2013, :EDUCPL])
89+
combine(nrow => :Count)
90+
end
91+
92+
primary = [12, 20]
93+
secondary = [40, 41, 42, 43, 50]
94+
university = [70, 71, 72, 73]
95+
96+
edu_df.EDUCPL = convert(Vector{Any}, edu_df.EDUCPL)
97+
replace!(x -> in(x, primary) ? "PRIMARY" : x, edu_df.EDUCPL)
98+
replace!(x -> in(x, secondary) ? "SECONDARY" : x, edu_df.EDUCPL)
99+
replace!(x -> in(x, university) ? "UNIVERSITY" : x, edu_df.EDUCPL)
100+
101+
edu_counts = @chain edu_df begin
102+
filter!(row -> !isa(row.EDUCPL, Real), _)
103+
groupby([:ENUTS2_2013, :EDUCPL])
104+
combine(:Count => sum => :Count)
105+
groupby([:EDUCPL])
106+
end
107+
```
108+
109+
## 6. Visualize Educational Attainment Across Regions
110+
111+
The workflow keeps both map styles:
112+
113+
- rough exploration with multiple color schemes (`:Purples`, `:Greens`, `:Blues`, `:Reds`)
114+
- final cleaned map with `:Wistia` and a colorbar
115+
116+
Inside the plotting loop, counts are normalized to the 0-1 range. This improves comparability between
117+
education categories and across regions, independent of absolute category sizes.
118+
119+
```julia
120+
# Base region map
121+
fig_regions = Figure(size = (1200, 1400), fontsize = 20)
122+
ax_regions = CairoMakie.Axis(fig_regions[1, 1])
123+
poly!(ax_regions, geodf.geometry, color = 1:16, colormap = :Reds, strokecolor = :black, strokewidth = 3)
124+
Label(fig_regions[:, :, Top()], "Voivodeships of Poland", fontsize = 50)
125+
hidedecorations!(ax_regions)
126+
127+
# Multi-color comparison map
128+
fig_rough = Figure(size = (1200, 1400), fontsize = 20)
129+
axs_rough = [Axis(fig_rough[x, y]) for x in 1:2 for y in 1:2]
130+
colors = [:Purples, :Greens, :Blues, :Reds]
131+
poly!(axs_rough[1], geodf.geometry, color = :white, strokecolor = :black, strokewidth = 3)
132+
hidedecorations!(axs_rough[1])
133+
axs_rough[1].title = "Voivodeships of Poland"
134+
Label(fig_rough[:, :, Top()], "Normalized Education Counts across Poland", fontsize = 50, padding = (0, 0, 30, 0))
135+
136+
for (idx, counts) in enumerate(edu_counts)
137+
geo_counts = outerjoin(counts, geodf; on = [:ENUTS2_2013 => :ENUTS2])
138+
norm_counts = (geo_counts.Count .- minimum(geo_counts.Count)) / (maximum(geo_counts.Count) .- minimum(geo_counts.Count))
139+
cmap = cgrad(colors[idx + 1], norm_counts)
140+
dropmissing!(geo_counts, :geometry)
141+
ax = axs_rough[idx + 1]
142+
poly!(ax, geo_counts.geometry, color = cmap[norm_counts], strokecolor = :black, strokewidth = 3)
143+
ax.title = "$(counts.EDUCPL |> first)"
144+
hidedecorations!(ax)
145+
end
146+
147+
# Final cleaned visualization with a colorbar
148+
fig = Figure(size = (1200, 1400), fontsize = 20)
149+
axs = [Axis(fig[x, y]) for x in 1:2 for y in 1:2]
150+
151+
poly!(axs[1], geodf.geometry, color = :white, strokecolor = :black, strokewidth = 2)
152+
axs[1].title = "Voivodeships of Poland"
153+
hidedecorations!(axs[1])
154+
Label(fig[:, :, Top()], "Normalized Educational Attainment across Poland", fontsize = 50, padding = (0,0,30,0))
155+
156+
for (idx, counts) in enumerate(edu_counts)
157+
geo_counts = outerjoin(counts, geodf; on = [:ENUTS2_2013 => :ENUTS2])
158+
norm_counts = (geo_counts.Count .- minimum(geo_counts.Count)) / (maximum(geo_counts.Count) .- minimum(geo_counts.Count))
159+
160+
cmap = cgrad(:Wistia, norm_counts)
161+
dropmissing!(geo_counts, :geometry)
162+
163+
ax = axs[idx + 1]
164+
poly!(ax, geo_counts.geometry, color = cmap[norm_counts], strokecolor = :black, strokewidth = 2)
165+
ax.title = "$(counts.EDUCPL |> first)"
166+
hidedecorations!(ax)
167+
end
168+
169+
Colorbar(fig[:, 3], limits = (0, 1), colormap = :Wistia)
170+
171+
save(joinpath(OUTPUT_DIR, "voivodeships_poland.png"), fig_regions)
172+
save(joinpath(OUTPUT_DIR, "education_rough_multicolors.png"), fig_rough)
173+
save(joinpath(OUTPUT_DIR, OUTPUT_FIGURE), fig)
174+
```
175+
176+
Additional outputs produced by the scripted workflow:
177+
178+
- `output/voivodeships_poland.png`
179+
- `output/education_rough_multicolors.png`
180+
- `output/education_choropleth_poland.png`
181+
182+
## Reproducible Scripted Run
183+
184+
From the workflow directory:
185+
186+
```bash
187+
julia --project=. -e "using Pkg; Pkg.instantiate()"
188+
copy config.toml.example config.toml
189+
julia --project=. run.jl
190+
```
191+
192+
On macOS/Linux, replace `copy` with `cp`.
193+
194+
No database is required for this workflow. Inputs are local files configured in `config.toml`:
195+
196+
- IPUMS DDI metadata (`.xml`)
197+
- IPUMS extract data (`.dat`)
198+
- NHGIS shapefile (`.shp` and companion files)
199+
200+
The scripted workflow is implemented in:
201+
202+
- `src/workflows/geospatial_census/src/01_load_data.jl`
203+
- `src/workflows/geospatial_census/src/02_preprocess.jl`
204+
- `src/workflows/geospatial_census/src/03_visualize.jl`
205+
206+
This keeps acquisition, preprocessing, integration, and visualization steps explicit and reproducible.

docs/src/geospatial.md

Lines changed: 0 additions & 5 deletions
This file was deleted.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
output/
2+
config.toml
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
name = "GeospatialCensusWorkflow"
2+
authors = ["JuliaHealth contributors"]
3+
4+
[deps]
5+
CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0"
6+
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
7+
Chain = "8be319e6-bccf-4806-a6f7-6fae938471bc"
8+
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
9+
GeoDataFrames = "62cb38b5-d8d2-4862-a48e-6a340996859f"
10+
GeoInterfaceMakie = "0edc0954-3250-4c18-859d-ec71c1660c08"
11+
GeoMakie = "db073c08-6b98-4ee5-b6a4-5efafb3259c6"
12+
IPUMS = "51d1f77e-d457-4c14-a89d-9ed71839f38d"
13+
StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
14+
15+
[compat]
16+
julia = "1.10"

src/workflows/geospatial_census/README.md

Lines changed: 0 additions & 2 deletions
This file was deleted.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
using Pkg
2+
Pkg.activate(@__DIR__)
3+
Pkg.add(url="https://github.com/JuliaHealth/IPUMS.jl.git")
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Geospatial Census Workflow Configuration
2+
#
3+
# This file documents the expected data paths for the IPUMS Poland workflow.
4+
# Copy this to config.toml and update paths to match your local data.
5+
#
6+
# Data Sources:
7+
# - IPUMS International: https://international.ipums.org/
8+
# - IPUMS NHGIS: https://www.nhgis.org/
9+
#
10+
# To set up:
11+
# 1. Create a directory `poland_data/` in the workflow root
12+
# 2. Download IPUMS extract (XML and DAT files)
13+
# 3. Create a directory `shapefiles/` in the workflow root
14+
# 4. Download ENUTS2_2013 shapefile from IPUMS NHGIS
15+
16+
[data]
17+
# Path to IPUMS census data DDI metadata file (XML)
18+
ipums_ddi = "poland_data/ipumsi_00001.xml"
19+
20+
# Path to IPUMS census data extract (DAT)
21+
ipums_dat = "poland_data/ipumsi_00001.dat"
22+
23+
# Path to ENUTS2 shapefile for Poland regions
24+
shapefile = "shapefiles/ENUTS2_2013.shp"
25+
26+
[output]
27+
# Directory where results will be saved
28+
dir = "output"
29+
30+
# Figure filename
31+
figure = "education_choropleth_poland.png"
Binary file not shown.

0 commit comments

Comments
 (0)