Skill Tracer
Skills › Scientific Agent Skills › geopandas › visualization.md

visualization.md

253 lines · skills/geopandas/references/visualization.md at 5f2bc03

Open on GitHub

Most marks come from matching words in the file, so treat them as pointers.

1# Static and interactive visualization
2
3A map is a derived analytical artifact. It can misstate units, hide missing
4data, expose exact locations, or contact third-party services even when the
5underlying geometry operations are correct.
6

Static and interactive visualization

A map is a derived analytical artifact. It can misstate units, hide missing data, expose exact locations, or contact third-party services even when the underlying geometry operations are correct.

Raw lines 1 to 6
7## Privacy gate
8
9Before plotting:
111. classify coordinates, addresses, trajectories, parcels, facilities, and
12 small-area attributes for sensitivity;
132. decide whether the audience needs exact geometry;
143. aggregate, suppress small groups, jitter only with a defensible privacy
15 model, or generalize at an appropriate scale;
164. remove direct identifiers and sensitive tooltip/popup fields;
175. inspect the output itself—HTML, SVG, PDF, and GeoJSON can preserve exact
18 coordinates or attributes even when the image looks coarse.
20The bundled `scripts/sensitive_coordinates_checklist.py` provides a conservative
21release gate. It does not claim legal or privacy compliance.

Privacy gate

Before plotting:

  1. classify coordinates, addresses, trajectories, parcels, facilities, and small-area attributes for sensitivity;
  2. decide whether the audience needs exact geometry;
  3. aggregate, suppress small groups, jitter only with a defensible privacy model, or generalize at an appropriate scale;
  4. remove direct identifiers and sensitive tooltip/popup fields;
  5. inspect the output itself—HTML, SVG, PDF, and GeoJSON can preserve exact coordinates or attributes even when the image looks coarse.

The bundled scripts/sensitive_coordinates_checklist.py provides a conservative release gate. It does not claim legal or privacy compliance.

Raw lines 7 to 22
Runs code scripts/sensitive_coordinates_checklist.py :20
23## Static plotting
25GeoPandas `.plot()` uses Matplotlib:
27```python
28import matplotlib.pyplot as plt
30fig, ax = plt.subplots(figsize=(8, 6))
31gdf.plot(
32 ax=ax,
33 column="rate",
34 cmap="viridis",
35 legend=True,
36 missing_kwds={
37 "color": "lightgrey",
38 "edgecolor": "black",
39 "hatch": "///",
40 "label": "Missing",
41 },
43ax.set_title("Synthetic rate by region")
44ax.set_axis_off()
45fig.savefig("derived/map.png", dpi=300, bbox_inches="tight")
46```
48Pin Matplotlib and optional mapping dependencies in the project lock. The
49GeoPandas 1.2 tagged source requires Matplotlib >=3.9 for plotting and
50mapclassify >=2.7 for classifications. Static examples were exercised with
51Matplotlib 3.11.2; the optional classification, interactive, and tile examples
52are illustrative unless those dependencies are installed and tested.
54GeoPandas 1.2 rewrote static plotting: categorical palettes/styles accept
55dictionaries, legends reflect geometry types, and layered maps can use
56`ax.legend()`. CRS-based axis labels are enabled by default (`add_labels=False`
57disables them). `scheme=` supports `vmin`/`vmax` and can use
58`legend_kwds={"colorbar": True}` for a classified colorbar. Inspect existing
59figures after migration; Matplotlib collection layout is not a stable contract.

Static plotting

GeoPandas .plot() uses Matplotlib:

import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(8, 6))
gdf.plot(
    ax=ax,
    column="rate",
    cmap="viridis",
    legend=True,
    missing_kwds={
        "color": "lightgrey",
        "edgecolor": "black",
        "hatch": "///",
        "label": "Missing",
    },
)
ax.set_title("Synthetic rate by region")
ax.set_axis_off()
fig.savefig("derived/map.png", dpi=300, bbox_inches="tight")

Pin Matplotlib and optional mapping dependencies in the project lock. The GeoPandas 1.2 tagged source requires Matplotlib >=3.9 for plotting and mapclassify >=2.7 for classifications. Static examples were exercised with Matplotlib 3.11.2; the optional classification, interactive, and tile examples are illustrative unless those dependencies are installed and tested.

GeoPandas 1.2 rewrote static plotting: categorical palettes/styles accept dictionaries, legends reflect geometry types, and layered maps can use ax.legend(). CRS-based axis labels are enabled by default (add_labels=False disables them). scheme= supports vmin/vmax and can use legend_kwds={"colorbar": True} for a classified colorbar. Inspect existing figures after migration; Matplotlib collection layout is not a stable contract.

Raw lines 23 to 60
61### Choropleth correctness
63State in the title/caption or metadata:
65- variable definition, units, date, and source;
66- whether values are counts, rates, densities, or percentages;
67- normalization denominator and treatment of zero/missing denominators;
68- classification method, number of bins, and explicit boundaries;
69- color-map direction and meaning;
70- missing/suppressed/out-of-scope styling;
71- CRS/projection and any geographic generalization.
73Raw counts over unequal polygon areas often communicate population size rather
74than rate. Compute an appropriate rate/density first and retain numerator and
75denominator.
77`scheme=` delegates classification to mapclassify:
79```python
80ax = gdf.plot(
81 column="rate",
82 scheme="quantiles",
83 k=5,
84 cmap="viridis",
85 legend=True,
87```
89Quantiles balance feature counts but can place almost equal values in different
90bins. Equal intervals can leave sparse bins; natural breaks are data-dependent
91and make cross-map comparisons difficult. For comparisons, reuse explicit
92boundaries and a common normalization.

Choropleth correctness

State in the title/caption or metadata:

  • variable definition, units, date, and source;
  • whether values are counts, rates, densities, or percentages;
  • normalization denominator and treatment of zero/missing denominators;
  • classification method, number of bins, and explicit boundaries;
  • color-map direction and meaning;
  • missing/suppressed/out-of-scope styling;
  • CRS/projection and any geographic generalization.

Raw counts over unequal polygon areas often communicate population size rather than rate. Compute an appropriate rate/density first and retain numerator and denominator.

scheme= delegates classification to mapclassify:

ax = gdf.plot(
    column="rate",
    scheme="quantiles",
    k=5,
    cmap="viridis",
    legend=True,
)

Quantiles balance feature counts but can place almost equal values in different bins. Equal intervals can leave sparse bins; natural breaks are data-dependent and make cross-map comparisons difficult. For comparisons, reuse explicit boundaries and a common normalization.

Raw lines 61 to 93
94### Missing data
96GeoPandas ignores missing values by default. That can make missing areas look
97like background or water. Always count missing values and use `missing_kwds`
98when they are meaningful. Distinguish:
100- missing attribute;
101- missing geometry;
102- empty geometry;
103- suppressed value;
104- zero;
105- outside the study area.
107Do not coerce missing values to zero for visual convenience.

Missing data

GeoPandas ignores missing values by default. That can make missing areas look like background or water. Always count missing values and use missing_kwds when they are meaningful. Distinguish:

  • missing attribute;
  • missing geometry;
  • empty geometry;
  • suppressed value;
  • zero;
  • outside the study area.

Do not coerce missing values to zero for visual convenience.

Raw lines 94 to 108
109### Categorical maps
111Use `categorical=True` for categories and a qualitative palette. Verify category
112order and legend labels; do not imply magnitude with a sequential palette.
113For example, the following explicit palette is supported in 1.2:
115```python
116ax = gdf.plot(
117 column="category", categorical=True,
118 cmap={"A": "#4477AA", "B": "#EE6677"},
119 legend=True, tiles=False,
121```
123Keep the palette complete for the data's categories. The earlier 1.1.4 fix for
124custom categorical/boolean `explore()` legend labels is included.

Categorical maps

Use categorical=True for categories and a qualitative palette. Verify category order and legend labels; do not imply magnitude with a sequential palette. For example, the following explicit palette is supported in 1.2:

ax = gdf.plot(
    column="category", categorical=True,
    cmap={"A": "#4477AA", "B": "#EE6677"},
    legend=True, tiles=False,
)

Keep the palette complete for the data's categories. The earlier 1.1.4 fix for custom categorical/boolean explore() legend labels is included.

Raw lines 109 to 125
126### Layering
128```python
129fig, ax = plt.subplots(figsize=(8, 6))
130areas.plot(ax=ax, facecolor="none", edgecolor="0.4", zorder=1)
131generalized_points.plot(ax=ax, color="black", markersize=8, zorder=2)
132```
134- Reproject every layer to a common CRS.
135- Use `facecolor="none"` for transparent polygon faces; Python `None` means
136 something different.
137- Choose `zorder`, alpha, and line width deliberately.
138- Confirm no layer is hidden by a filled polygon.
139- Keep a stable visual scale when comparing panels.

Layering

fig, ax = plt.subplots(figsize=(8, 6))
areas.plot(ax=ax, facecolor="none", edgecolor="0.4", zorder=1)
generalized_points.plot(ax=ax, color="black", markersize=8, zorder=2)
  • Reproject every layer to a common CRS.
  • Use facecolor="none" for transparent polygon faces; Python None means something different.
  • Choose zorder, alpha, and line width deliberately.
  • Confirm no layer is hidden by a filled polygon.
  • Keep a stable visual scale when comparing panels.
Raw lines 126 to 140
141## Projection and extent
143Choose a map projection for the communication goal:
145- equal-area for area comparisons;
146- local conformal for local shape/angles;
147- Web Mercator only when required by a web tile system;
148- a suitable global projection for world views.
150GeoPandas may set a latitude-dependent aspect for geographic plots, but that is
151not a replacement for a chosen projection. Exact metric scale bars require a
152projected CRS with known linear units and limited distortion.
154Antimeridian-crossing geometries must be split/wrapped before plotting; changing
155axis limits does not repair a line drawn across the map.

Projection and extent

Choose a map projection for the communication goal:

  • equal-area for area comparisons;
  • local conformal for local shape/angles;
  • Web Mercator only when required by a web tile system;
  • a suitable global projection for world views.

GeoPandas may set a latitude-dependent aspect for geographic plots, but that is not a replacement for a chosen projection. Exact metric scale bars require a projected CRS with known linear units and limited distortion.

Antimeridian-crossing geometries must be split/wrapped before plotting; changing axis limits does not repair a line drawn across the map.

Raw lines 141 to 156
157## Basemaps are network and licensing dependencies
159GeoPandas 1.2 static `plot(tiles=...)`, contextily, and `explore(tiles=...)` can send viewport,
160zoom, IP, and timing information to a provider and can disclose the study area.
161They also introduce attribution, terms-of-use, caching, availability, and
162reproducibility requirements.
164Static plots default to `tiles=False`; interactive drafts use `tiles=None`.
165Do not fetch tiles automatically. If a user explicitly approves a provider:
167- verify its official URL, license, attribution, and usage limits;
168- generalize sensitive overlays before the request;
169- record provider, style, retrieval date, zoom, and tile hashes/cache;
170- do not put credentials in source or generated HTML;
171- use a vetted cache for reproducible/offline output where permitted.

Basemaps are network and licensing dependencies

GeoPandas 1.2 static plot(tiles=...), contextily, and explore(tiles=...) can send viewport, zoom, IP, and timing information to a provider and can disclose the study area. They also introduce attribution, terms-of-use, caching, availability, and reproducibility requirements.

Static plots default to tiles=False; interactive drafts use tiles=None. Do not fetch tiles automatically. If a user explicitly approves a provider:

  • verify its official URL, license, attribution, and usage limits;
  • generalize sensitive overlays before the request;
  • record provider, style, retrieval date, zoom, and tile hashes/cache;
  • do not put credentials in source or generated HTML;
  • use a vetted cache for reproducible/offline output where permitted.
Raw lines 157 to 172
173## Interactive `explore`
175`GeoDataFrame.explore()` returns a `folium.Map`. Background tiles require CRS
176metadata and normally cause network requests. Start with a no-tile,
177no-attribute local draft:
179```python
180interactive = generalized_gdf.explore(
181 tiles=None,
182 tooltip=False,
183 popup=False,
184 style_kwds={"fillOpacity": 0.5, "weight": 1},
186interactive.save("derived/local-draft.html")
187```
189Even `tiles=None` HTML can reference CDN-hosted JavaScript/CSS depending on the
190Folium configuration. Inspect the generated HTML and use an approved offline
191asset strategy before sensitive or air-gapped use.
193Privacy hazards:
195- `popup=True` can expose all columns;
196- tooltip lists can expose addresses or unique IDs;
197- coordinates are embedded in the HTML/GeoJSON;
198- layer names and filenames can disclose project context;
199- user-added tile URLs can leak tokens and viewport;
200- sharing the HTML shares data, not just a screenshot.
202For multiple layers, add only generalized/allowlisted columns and explicit
203names. Do not use the interactive map as a data-access control boundary.

Interactive explore

GeoDataFrame.explore() returns a folium.Map. Background tiles require CRS metadata and normally cause network requests. Start with a no-tile, no-attribute local draft:

interactive = generalized_gdf.explore(
    tiles=None,
    tooltip=False,
    popup=False,
    style_kwds={"fillOpacity": 0.5, "weight": 1},
)
interactive.save("derived/local-draft.html")

Even tiles=None HTML can reference CDN-hosted JavaScript/CSS depending on the Folium configuration. Inspect the generated HTML and use an approved offline asset strategy before sensitive or air-gapped use.

Privacy hazards:

  • popup=True can expose all columns;
  • tooltip lists can expose addresses or unique IDs;
  • coordinates are embedded in the HTML/GeoJSON;
  • layer names and filenames can disclose project context;
  • user-added tile URLs can leak tokens and viewport;
  • sharing the HTML shares data, not just a screenshot.

For multiple layers, add only generalized/allowlisted columns and explicit names. Do not use the interactive map as a data-access control boundary.

Raw lines 173 to 204
205## Geometry and plotting edge cases
207- Empty/missing geometries are not visible; report their counts.
208- Invalid polygons can render inconsistently; validate first.
209- Mixed geometry types need explicit styles per type.
210- Polygon holes and ring orientation should be checked after repair/export.
211- Z/M are ignored by 2D plotting.
212- Marker size is in display units, not map units, unless explicitly transformed.
213- Alpha blending can create misleading dark areas from duplicated/overlapping
214 features; audit duplicate geometry and join multiplication.
215- Tiny overlay slivers can dominate outlines; fix/audit topology rather than
216 merely hiding them.

Geometry and plotting edge cases

  • Empty/missing geometries are not visible; report their counts.
  • Invalid polygons can render inconsistently; validate first.
  • Mixed geometry types need explicit styles per type.
  • Polygon holes and ring orientation should be checked after repair/export.
  • Z/M are ignored by 2D plotting.
  • Marker size is in display units, not map units, unless explicitly transformed.
  • Alpha blending can create misleading dark areas from duplicated/overlapping features; audit duplicate geometry and join multiplication.
  • Tiny overlay slivers can dominate outlines; fix/audit topology rather than merely hiding them.
Raw lines 205 to 217
218## Accessibility and honest design
220- Prefer perceptually uniform, color-vision-aware palettes.
221- Include text labels/patterns when color alone is insufficient.
222- Keep legend order, labels, precision, and units consistent with the data.
223- Avoid rainbow palettes and excessive classes.
224- Use adequate contrast and minimum line/marker sizes.
225- Include alt text/caption summarizing the main pattern and missing/suppressed
226 data.
227- Do not imply uncertainty-free precision; display uncertainty or caveats.

Accessibility and honest design

  • Prefer perceptually uniform, color-vision-aware palettes.
  • Include text labels/patterns when color alone is insufficient.
  • Keep legend order, labels, precision, and units consistent with the data.
  • Avoid rainbow palettes and excessive classes.
  • Use adequate contrast and minimum line/marker sizes.
  • Include alt text/caption summarizing the main pattern and missing/suppressed data.
  • Do not imply uncertainty-free precision; display uncertainty or caveats.
Raw lines 218 to 228
229## Reproducible output
231Record:
233- source/output hashes and privacy/generalization decision;
234- package versions, CRS, projection, extent, and antimeridian handling;
235- plotted column, normalization, classification/bins, palette, missing style;
236- layer order and styling;
237- figure size, DPI, and format;
238- tile provider/license/retrieval metadata or explicit `tiles=None`;
239- tooltip/popup allowlist and HTML external-resource audit.
241Raster PNG reduces direct coordinate extraction compared with SVG/HTML but is
242not anonymization. Check metadata and visual landmarks before release.

Reproducible output

Record:

  • source/output hashes and privacy/generalization decision;
  • package versions, CRS, projection, extent, and antimeridian handling;
  • plotted column, normalization, classification/bins, palette, missing style;
  • layer order and styling;
  • figure size, DPI, and format;
  • tile provider/license/retrieval metadata or explicit tiles=None;
  • tooltip/popup allowlist and HTML external-resource audit.

Raster PNG reduces direct coordinate extraction compared with SVG/HTML but is not anonymization. Check metadata and visual landmarks before release.

Raw lines 229 to 243
244## Sources (verified 2026-10-01)
246- [GeoPandas mapping and plotting guide](https://geopandas.org/en/stable/docs/user_guide/mapping.html).
247- [GeoPandas interactive mapping guide](https://geopandas.org/en/stable/docs/user_guide/interactive_mapping.html).
248- [GeoDataFrame.plot API](https://geopandas.org/en/stable/docs/reference/api/geopandas.GeoDataFrame.plot.html).
249- [GeoDataFrame.explore API](https://geopandas.org/en/stable/docs/reference/api/geopandas.GeoDataFrame.explore.html).
250- [GeoPandas 1.1.4 release notes](https://github.com/geopandas/geopandas/releases/tag/v1.1.4) — released 2026-06-26.
251- [GeoPandas 1.1.0 release notes](https://github.com/geopandas/geopandas/releases/tag/v1.1.0) — plotting and dependency changes.
253- [GeoPandas 1.2.0 plotting implementation](https://github.com/geopandas/geopandas/blob/v1.2.0/geopandas/plotting.py).