🚧 Under construction — VoTechWorks is being built. A free tech program — complimentary, as our commitment to get America working.
voTechWorksGrade 13 · a vocational technology learning center, by urbanicity
newSchoolWorks.com › VoTechWorks › Technology & Design › Data Science & Analytics › Capstone with Real City/Urbanicity Data › Lesson 5
TEC-DATA85-11 · Lesson 5 · 12 clock hours · lab

Spatial Analysis and Mapping Urban Patterns

Objective. Students perform at least one spatial join or geographic aggregation on their city data and produce a choropleth or point-density map that visually communicates a geographic pattern relevant to their research question.

⚠ Pending professional review

This lesson, its worked examples and its practice questions has not yet been verified by an industry-certified professional or by a certification or approval body. It is a study draft and may contain errors. Do not use it as instructions for work on a job site or on live equipment. Always follow your instructor, your supervising professional, and current official references and regulations.

VoTechWorks program approval and accreditation are still in progress. Credentials are issued only by the certifying bodies named in each program, after their own exams.

Spatial Analysis and Mapping Urban Patterns

Spatial analysis adds a geographic dimension to data science: instead of asking only what happened, you ask where it happened and whether location explains a pattern. City and urbanicity data—inspections, permits, 311 calls, demographic counts—almost always carry an address or coordinate that can be mapped, joined, and aggregated by geography. This lab brings those skills together in your capstone project.

Why Geography Matters in Urban Data

Two neighborhoods can look identical in a table of averages but tell very different stories on a map. A restaurant inspection failure rate of 12 % means one thing city-wide and something very different when you see it concentrated in three census tracts near an industrial corridor. Choropleth maps and density maps surface that spatial structure instantly. Policymakers, planners, and community groups read maps fluently, so a well-made map is often the most persuasive deliverable in an urban data project.

Coordinate Reference Systems (CRS)

Every spatial dataset lives in a coordinate reference system—a mathematical model that relates coordinates on a curved Earth to a flat map or to a 3-D globe. If two layers use different CRS values, a spatial join or overlay will silently produce wrong results or raise a geometry mismatch error. Before any spatial operation, confirm that all layers share the same CRS.

Two common families matter for US city work:

In GeoPandas, check and reproject with:

```python

print(gdf.crs) # check current CRS

gdf = gdf.to_crs(epsg=26918) # reproject to UTM zone 18N, for example

```

Document your CRS choice and the reason for any reprojection in your project notebook.

Spatial Join

A spatial join attaches the attributes of one layer to the features of another based on their geometric relationship (intersects, within, contains, nearest). The most common urban-data pattern joins point records (inspections, incidents) to polygon features (census tracts, zip codes) so you can count or aggregate events by area.

```python

import geopandas as gpd

tracts = gpd.read_file('census_tracts.shp').to_crs(epsg=26918)

points = gpd.read_file('inspections.geojson').to_crs(epsg=26918)

joined = gpd.sjoin(points, tracts, how='left', predicate='within')

counts = joined.groupby('GEOID')['inspection_id'].count().reset_index()

counts.columns = ['GEOID', 'inspection_count']

tracts = tracts.merge(counts, on='GEOID', how='left')

tracts['rate_per_1000'] = (tracts['inspection_count'] / tracts['POP']) * 1000

```

Always verify that the join produced a sensible row count and check for null geometry rows before proceeding.

Buffer Analysis

A buffer creates a zone of a specified distance around each feature. Buffers are useful for proximity questions: How many food vendors are within 500 meters of a school? Apply buffers in a projected CRS so the distance unit is meters or feet, not degrees.

```python

schools_proj = schools.to_crs(epsg=26918)

school_buffers = schools_proj.copy()

school_buffers['geometry'] = schools_proj.geometry.buffer(500) # 500 m

vendors_near = gpd.sjoin(vendors.to_crs(epsg=26918), school_buffers,

how='inner', predicate='within')

```

Choropleth Maps

A choropleth map shades polygons by a numeric variable. Best practices:

1. Normalize your variable. Map rates or densities (per capita, per square kilometer), not raw counts, unless polygon sizes are roughly equal.

2. Choose a classification scheme appropriate to your data distribution: equal intervals, quantiles, or natural breaks (Jenks).

3. Select a color palette intentionally. Sequential palettes (light to dark single hue) suit one-directional data. Diverging palettes (two hues meeting at a neutral midpoint) suit data centered on a meaningful reference value such as the city average.

4. Test for color-blind accessibility. Avoid red-green combinations. Tools like ColorBrewer 2.0 label palettes as colorblind-safe.

5. Include all required map elements: title, legend with value ranges and units, north arrow or orientation note, data source credit, and projection note.

In GeoPandas and Matplotlib:

```python

import matplotlib.pyplot as plt

fig, ax = plt.subplots(1, 1, figsize=(10, 8))

tracts.plot(column='rate_per_1000', cmap='RdBu_r', scheme='quantiles',

legend=True, ax=ax, missing_kwds={'color': 'lightgrey'})

ax.set_title('Inspection Rate per 1,000 Residents by Census Tract')

ax.set_axis_off()

plt.tight_layout()

plt.savefig('choropleth_map.png', dpi=150)

```

Data Safety and Integrity

Spatial work introduces specific data-quality risks that function as the "safety" layer of GIS practice:

Peer Review and Revision

Before submitting, post your map to the class board and use the five-item checklist (title, legend, north arrow or orientation note, data source credit, color accessibility) to evaluate two classmates' maps, and incorporate their written feedback on yours. Peer critique catches omissions that you stop seeing after hours of your own work. Revision based on feedback is part of the grade.

Key terms9

Coordinate Reference System (CRS)A framework that uses coordinates to define locations on Earth's surface; every spatial dataset has one, and layers must share the same CRS before being joined or overlaid.
Spatial joinAn operation that combines attributes from two geographic layers based on their geometric relationship—such as which points fall within which polygon.
Choropleth mapA thematic map that shades or colors polygons according to the value of a variable, used to show geographic variation across areas such as census tracts.
Buffer analysisA spatial operation that creates a zone of a specified distance around a feature (point, line, or polygon), used to answer proximity questions.
Normalization (spatial)Dividing a raw count by a relevant denominator—such as population or area—so that choropleth shading reflects rates or densities rather than totals, enabling fair comparison across polygons of different sizes.
Kernel density estimation (KDE)A spatial technique that estimates the intensity of point events across a surface, producing a smooth density raster that highlights concentrations without requiring predefined polygon boundaries.
TIGER shapefilesBoundary and geographic feature files published by the US Census Bureau, commonly used as the polygon layer for census tracts, counties, and other administrative units in US urban analysis.
Diverging color schemeA color palette built from two contrasting hues meeting at a neutral midpoint, appropriate when data has a meaningful center value and the analyst wants to highlight departures in both directions.
ProjectionA mathematical transformation that converts the curved surface of the Earth to a flat plane; choosing the right projection for your region ensures accurate distance and area measurements.

Worked examples4

1. A census tract has a population of 4,200 residents and recorded 63 restaurant inspection failures last year. Calculate the failure rate per 1,000 residents.
  1. Write the formula: rate per 1,000 = (count / population) × 1,000.
  2. Substitute values: (63 / 4,200) × 1,000.
  3. Divide first: 63 / 4,200 = 0.015.
  4. Multiply by 1,000: 0.015 × 1,000 = 15.
Answer: 15 inspection failures per 1,000 residents.
2. You have a school point layer and want to buffer each school by 0.25 miles. Your projected CRS uses meters. Convert 0.25 miles to meters to pass the correct value to the buffer function.
  1. Recall the conversion: 1 mile = 1,609.344 meters.
  2. Multiply: 0.25 × 1,609.344 = 402.336 meters.
  3. Round to a practical precision: 402 meters (or 402.3 meters if precision matters).
  4. Pass this value: `geometry.buffer(402.336)`.
Answer: 402.336 meters (approximately 402 m); use this value in the GeoPandas buffer call.
3. After a spatial join of 1,850 inspection points to census tract polygons, your grouped count shows only 1,743 inspections assigned to tracts. Identify the likely cause and the corrective step.
  1. The difference is 1,850 − 1,743 = 107 points unassigned.
  2. The most likely cause is a CRS mismatch: if points and tracts were in different coordinate systems, many points would fall outside all polygons geometrically.
  3. A secondary cause is points that genuinely lie outside the tract boundaries (e.g., on a boundary edge or outside the city limits).
  4. Corrective step: confirm both layers share the same CRS (print `gdf.crs` for each), reproject if needed using `to_crs()`, re-run the join, and inspect the unmatched rows with `joined[joined['GEOID'].isna()]` to decide how to handle boundary cases.
Answer: 107 points were not matched. The most likely cause is a CRS mismatch; reprojecting both layers to the same CRS and re-running the join should recover most or all of the missing records.
4. Your city has 38 census tracts. After merging inspection counts back to the tracts GeoDataFrame, 5 tracts show NaN for inspection_count. What rate_per_1000 value will Pandas assign those tracts, and how should you handle them on the choropleth map?
  1. Pandas arithmetic on NaN produces NaN; those 5 tracts will have NaN for rate_per_1000.
  2. On the choropleth, pass `missing_kwds={'color': 'lightgrey'}` (or similar) to GeoPandas so NaN tracts render as a neutral color rather than being invisible or raising an error.
  3. Add a legend note or map annotation indicating that grey tracts have no inspection records for the period, so readers do not interpret grey as zero.
  4. Optionally, investigate whether NaN tracts truly had zero inspections or whether the data extract missed them, and correct the underlying data if needed.
Answer: Pandas assigns NaN; the choropleth should render those tracts in a distinct neutral color (e.g., light grey) with a legend entry explaining the missing data.

The math in this lesson1

Learners compute inspection failures per 1,000 residents for each census tract.
7.RP.2
In class: Review of Course 9 GIS skills: instructor models joining census tract shapefile to city inspection data in GeoPandas, computing a per-capita rate by tract, and rendering a choropleth with a diverging color scheme and proper legend; students replicate with their own geometry layer. · Students apply at least one spatial operation (buffer analysis, spatial join, or kernel density estimation) meaningful to their research question and document the CRS (coordinate reference system) used and any projection choices in their project notebook. · Map critique exercise: students screenshot their map and post to a shared class board; two peers use a checklist (title, legend, north arrow or orientation note, data source credit, color accessibility for color-blind readers) to provide written feedback; students revise before submission.
Materials: Python 3 with geopandas, folium, matplotlib; QGIS 3 (optional); Census TIGER shapefiles; city-specific GIS portal layers; Course 9 spatial analysis notes; IBM Data Science Professional Certificate — data visualization module reference.
Check: Instructor reviews submitted map and notebook; pass requires correct CRS documented, spatial operation applied without geometry errors, map passes at least four of five checklist items, and a one-paragraph narrative explaining the geographic pattern — all criteria met = pass.

Practice check8

Answer each question, then check it. Nothing is recorded; this is your own practice.

Data Preparation and Analysis
1. Before performing a spatial join between a census tract polygon layer and a city inspection point layer in GeoPandas, what must you verify about both layers?
Data Analysis and Statistics
2. A census tract has 8,000 residents and 120 code violations recorded. What is the violation rate per 1,000 residents?
Data Visualization
3. You are creating a choropleth map showing which census tracts have inspection rates above and below the city-wide average. Which color scheme is most appropriate?
Data Preparation and Analysis
4. A student wants to find all grocery stores within 800 meters of each transit stop. Which spatial operation should they apply first?
Data Preparation and Analysis
5. When measuring buffer distances in GeoPandas, why is it important to use a projected CRS rather than a geographic CRS such as EPSG:4326?
Data Visualization
6. A peer's submitted map is missing which required element according to the five-item map checklist used in this lesson?
Data Governance and Ethics
7. Your dataset contains inspection records with resident home addresses. You want to publish a point map showing inspection locations. What is the correct privacy practice?
Data Preparation and Analysis
8. After running a spatial join, a data analyst notices that the joined GeoDataFrame has significantly fewer rows than the original point layer, with many rows having NaN values for the polygon attributes. What is the MOST likely explanation?
Record this lesson on my transcript. Sign in with your key and enrollment email to record lessons (enroll free if you have no key). This page stays open to read and practise.