Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Create maps, choropleths, and spatial data visualizations for research
.claude/skills/brycewang-stanford-geospatial-viz-guide/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-14 | ✗→✓ | ▲ Improved | 76% | 0% |
| case-04 | ✓→✗ | ▼ Worse | 48% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 73% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 97% | 0% |
| case-05 | ✓→✓ | = Same ✓ | 46% | 0% |
A skill for creating maps, choropleths, and spatial data visualizations for research publications. Covers coordinate systems, choropleth maps, point maps, Python geospatial libraries, and cartographic best practices for academic papers.
Vector data (discrete features):
- Shapefile (.shp): Legacy standard, multi-file
- GeoJSON (.geojson): Web-friendly, single file
- GeoPackage (.gpkg): Modern SQLite-based, recommended
- KML (.kml): Google Earth format
Raster data (continuous surfaces):
- GeoTIFF (.tif): Georeferenced image
- NetCDF (.nc): Climate and atmospheric data
- HDF5 (.h5): Satellite and remote sensing data
Key concepts:
- CRS (Coordinate Reference System): How 3D Earth maps to 2D
- EPSG:4326 (WGS84): Latitude/longitude (most GPS data)
- EPSG:3857: Web Mercator (Google Maps, web tiles)
- Always check and document your CRSpythonimport geopandas as gpd import matplotlib.pyplot as plt def create_choropleth(shapefile_path: str, data_column: str, title: str, cmap: str = "YlOrRd") -> None: """ Create a choropleth map from a shapefile. Args: shapefile_path: Path to shapefile or GeoPackage data_column: Column name to visualize title: Map title cmap: Matplotlib colormap name """ gdf = gpd.read_file(shapefile_path) fig, ax = plt.subplots(1, 1, figsize=(12, 8)) gdf.plot( column=data_column, cmap=cmap, linewidth=0.5, edgecolor="0.5", legend=True, legend_kwds={ "label": data_column, "orientation": "horizontal", "shrink": 0.6, "pad": 0.05 }, ax=ax ) ax.set_title(title, fontsize=14, fontweight="bold") ax.axis("off") plt.tight_layout() plt.savefig("choropleth.pdf", bbox_inches="tight", dpi=300)
pythonimport pandas as pd def join_data_to_map(gdf: gpd.GeoDataFrame, data: pd.DataFrame, geo_key: str, data_key: str) -> gpd.GeoDataFrame: """ Join tabular data to geographic features. Args: gdf: GeoDataFrame with polygons (e.g., country boundaries) data: DataFrame with your research data geo_key: Column in gdf to join on (e.g., 'ISO_A3') data_key: Column in data to join on (e.g., 'country_code') """ merged = gdf.merge(data, left_on=geo_key, right_on=data_key, how="left") missing = merged[merged[data.columns[1]].isna()] if len(missing) > 0: print(f"Warning: {len(missing)} regions have no data (will appear blank)") return merged
pythondef create_point_map(gdf_base: gpd.GeoDataFrame, points: gpd.GeoDataFrame, size_column: str = None, color_column: str = None) -> None: """ Create a point map with proportional symbols. Args: gdf_base: Base map (country or region polygons) points: GeoDataFrame with point geometries size_column: Column to scale point sizes color_column: Column to color points """ fig, ax = plt.subplots(figsize=(12, 8)) # Base map gdf_base.plot(ax=ax, color="lightgray", edgecolor="white", linewidth=0.5) # Points sizes = points[size_column] * 2 if size_column else 30 colors = points[color_column] if color_column else "red" points.plot( ax=ax, markersize=sizes, color=colors, alpha=0.6, edgecolor="black", linewidth=0.3 ) ax.axis("off") plt.tight_layout() plt.savefig("point_map.pdf", bbox_inches="tight", dpi=300)
pythonimport folium def create_interactive_map(center: tuple = (20, 0), zoom: int = 2) -> folium.Map: """ Create an interactive web map (useful for supplementary materials). Args: center: (latitude, longitude) center point zoom: Initial zoom level """ m = folium.Map(location=center, zoom_start=zoom, tiles="CartoDB positron") # Add markers, choropleth layers, or heatmaps as needed # folium.Marker([lat, lon], popup="Label").add_to(m) return m
1. Projection choice:
- Global maps: Robinson or Equal Earth (not Mercator for thematic maps)
- Country/region: Appropriate local projection
- Mercator distorts area -- misleading for choropleths
2. Color schemes:
- Sequential: Low-to-high values (YlOrRd, Blues, Viridis)
- Diverging: Values around a midpoint (RdBu, BrBG)
- Qualitative: Categorical data (Set2, Paired)
- Use colorbrewer2.org for perceptually uniform palettes
- Test for colorblind accessibility
3. Required map elements:
- Title
- Legend with units
- Scale bar
- North arrow (if orientation is non-standard)
- Data source attribution
- CRS/projection information
4. Ethical considerations:
- Disputed borders: Use dashed lines or note in caption
- Data gaps: Show "no data" regions explicitly (do not leave blank)
- Privacy: Aggregate point data to protect individual locations| Source | Data | Format | |--------|------|--------| | Natural Earth | Country/region boundaries, physical features | Shapefile, GeoJSON | | GADM | Administrative boundaries (all countries, all levels) | GeoPackage, Shapefile | | OpenStreetMap | Roads, buildings, land use | PBF, Shapefile | | WorldPop | Population density grids | GeoTIFF | | NASA SEDAC | Socioeconomic and environmental data | GeoTIFF, Shapefile | | USGS Earth Explorer | Satellite imagery, elevation | GeoTIFF |
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-14 | fail→pass | 16,165 | 16,790 | +4% | 1 | 1 | 0% | 2,446 | 4,313 | +76% | 0 | 0 | — |
case-01 | fail→fail | 26,076 | 34,139 | +31% | 1 | 1 | 0% | 5,016 | 7,481 | +49% | 0 | 0 | — |
case-02 | pass→pass | 14,377 | 16,403 | +14% | 1 | 1 | 0% | 2,572 | 4,461 | +73% | 0 | 0 | — |
case-03 | pass→pass | 12,181 | 11,633 | -4% | 1 | 1 | 0% | 1,893 | 3,731 | +97% | 0 | 0 | — |
case-04 | pass→fail | 15,472 | 12,196 | -21% | 1 | 1 | 0% | 2,585 | 3,818 | +48% | 0 | 0 | — |
case-05 | pass→pass | 15,360 | 12,600 | -18% | 1 | 1 | 0% | 2,794 | 4,079 | +46% | 0 | 0 | — |
case-06 | fail→fail | 17,005 | 18,192 | +7% | 1 | 1 | 0% | 2,490 | 4,734 | +90% | 0 | 0 | — |
case-07 | pass→pass | 15,035 | 13,246 | -12% | 1 | 1 | 0% | 2,619 | 4,118 | +57% | 0 | 0 | — |
case-08 | pass→pass | 16,243 | 14,770 | -9% | 1 | 1 | 0% | 2,604 | 4,196 | +61% | 0 | 0 | — |
case-09 | pass→pass | 12,981 | 12,041 | -7% | 1 | 1 | 0% | 2,193 | 3,703 | +69% | 0 | 0 | — |
case-10 | pass→pass | 15,146 | 11,733 | -23% | 1 | 1 | 0% | 2,440 | 3,566 | +46% | 0 | 0 | — |
case-11 | pass→pass | 11,650 | 10,589 | -9% | 1 | 1 | 0% | 1,740 | 3,440 | +98% | 0 | 0 | — |
case-12 | pass→pass | 8,301 | 6,929 | -17% | 1 | 1 | 0% | 1,263 | 2,803 | +122% | 0 | 0 | — |
case-13 | pass→pass | 8,208 | 4,182 | -49% | 1 | 1 | 0% | 1,212 | 2,260 | +86% | 0 | 0 | — |
case-15 | pass→pass | 14,948 | 15,320 | +2% | 1 | 1 | 0% | 2,255 | 3,988 | +77% | 0 | 0 | — |
case-16 | pass→pass | 15,852 | 17,751 | +12% | 1 | 1 | 0% | 2,379 | 4,766 | +100% | 0 | 0 | — |
case-17 | pass→pass | 12,722 | 8,646 | -32% | 1 | 1 | 0% | 2,011 | 3,170 | +58% | 0 | 0 | — |
case-18 | pass→pass | 13,439 | 12,111 | -10% | 1 | 1 | 0% | 2,171 | 3,825 | +76% | 0 | 0 | — |
case-19 | pass→pass | 17,528 | 17,679 | +1% | 1 | 1 | 0% | 2,915 | 4,500 | +54% | 0 | 0 | — |
case-20 | pass→pass | 4,368 | 2,587 | -41% | 1 | 1 | 0% | 645 | 2,016 | +213% | 0 | 0 | — |
case-21 | pass→pass | 3,416 | 3,567 | +4% | 1 | 1 | 0% | 488 | 2,183 | +347% | 0 | 0 | — |
case-22 | pass→pass | 4,140 | 3,428 | -17% | 1 | 1 | 0% | 715 | 2,243 | +214% | 0 | 0 | — |
case-23 | pass→pass | 10,072 | 5,391 | -46% | 1 | 1 | 0% | 1,523 | 2,578 | +69% | 0 | 0 | — |
case-24 | pass→pass | 17,961 | 16,043 | -11% | 1 | 1 | 0% | 2,757 | 4,692 | +70% | 0 | 0 | — |
case-25 | pass→pass | 15,462 | 17,117 | +11% | 1 | 1 | 0% | 2,260 | 4,181 | +85% | 0 | 0 | — |
case-26 | pass→pass | 9,724 | 8,659 | -11% | 1 | 1 | 0% | 1,653 | 3,166 | +92% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 26 cases were attempted. The headline lift of 0 percentage points is the difference between those two pass rates over the 26 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.