How each project is organized

Every project lives in its own folder, projects/<post-name>/, with the same parts:

  • README.md: what it does, where the data comes from, and how to run it
  • requirements.txt: the Python packages it needs
  • analysis.py: the script (some projects have numbered steps, like 01_download.py)
  • data/: the input data, or a script that downloads it
  • output/: the figures and tables the script creates, exactly what the post shows
Two maps showing combined sewer overflow outfalls as blue circles sized by average days with overflow per year, over orange overburdened communities, one for northern New Jersey and one for Camden and Gloucester City. Nearly all outfalls lie within overburdened communities, with large clusters along the Passaic and Hackensack waterways and Newark Bay.

When It Rains, It Pours Sewage: Combined Sewer Overflows in New Jersey

Supports: When It Rains, It Pours Sewage: Combined Sewer Overflows in New Jersey

When it rains, old combined sewers overflow into New Jersey rivers. I looked at how often outfalls discharge and who lives nearby.

Produces: 3 figures

  • Python
  • GeoPandas
  • matplotlib
  • NumPy
  • pandas
Two bar charts by 1930s HOLC grade A to D. Summer surface temperature relative to the map average rises from about 2 degrees Celsius below average in grade A to about 2.5 above in grade D, while present-day tree cover falls from about 77% in A to about 24% in D.

Mapped in 1937, Hotter in 2026: Redlining, Trees, and Heat in New Jersey

Supports: Mapped in 1937, Hotter in 2026: Redlining, Trees, and Heat in New Jersey

Neighborhoods redlined in the 1930s are hotter today, and tree cover explains most of the gap. Satellite heat and redlining in New Jersey.

Produces: 4 figures

  • Python
  • GeoPandas
  • matplotlib
  • NumPy
  • pandas
  • rasterio
  • rioxarray
  • statsmodels
Bar chart of lead and galvanized service lines reported statewide in New Jersey, falling each year from 163,382 in 2022 to 141,275, 135,252, 115,121, and 85,348 in 2026.

Getting the Lead Out: Pipes, Paint, and Children's Blood in New Jersey

Supports: Getting the Lead Out: Pipes, Paint, and Children's Blood in New Jersey

Lead pipes, old paint, and children's blood lead in New Jersey: where elevated levels are concentrated and what Newark and Trenton show about progress.

Produces: 3 figures

  • Python
  • GeoPandas
  • matplotlib
  • NumPy
  • pandas
  • SciPy
Map of New Jersey water system service areas colored by the higher of PFOA or PFOS level in EPA UCMR 5 sampling, from not detected to above 14 ng/L. The highest levels, shown in dark brown, appear mainly in northeastern Bergen and Essex counties and a few scattered systems, with many central and coastal areas at 4 to 14 ng/L.

Forever Chemicals, Everyone's Problem? PFAS in New Jersey's Drinking Water

Supports: Forever Chemicals, Everyone's Problem? PFAS in New Jersey's Drinking Water

PFAS in New Jersey drinking water are widely shared across the state. A look at federal testing results, who is affected, and who pays for treatment.

Produces: 3 figures

  • Python
  • GeoPandas
  • matplotlib
  • NumPy
  • pandas
Grouped bar chart of the share of residents within 1 km of a flood-exposed contaminated site under three definitions, for overburdened and other residents. Overburdened residents are about 2.6 to 3.2 times more likely, for example 25.2% versus 8.2% for dry land flooding at 3 feet or less of sea-level rise.

Toxic Tides: When Rising Seas Meet New Jersey's Contaminated Sites

Supports: Toxic Tides: When Rising Seas Meet New Jersey's Contaminated Sites

What happens when rising seas reach New Jersey's contaminated sites? I looked at which sites could flood and which communities live closest to them.

Produces: 5 figures

  • Python
  • GeoPandas
  • matplotlib
  • NumPy
  • pandas
  • statsmodels
Scree plot of the percent of variance explained by each of the first 10 principal components, with a cumulative line. The first component explains about 30% and the second about 10%, with the rest each under 6% and the cumulative total reaching about 75%.

Capstone: Building a Cumulative-Burden Index from the Data Up

Supports: Capstone: Building a Cumulative-Burden Index from the Data Up

Build a data-driven cumulative-burden index, compare it with NJDEP's count-based approach, and identify community types.

Produces: 3 figures and 3 tables

  • Python
  • GeoPandas
  • matplotlib
  • NumPy
  • pandas
  • scikit-learn
  • SciPy
Map of New Jersey showing large warehouse and industrial buildings from OpenStreetMap as dots sized by footprint, over orange overburdened communities. Buildings cluster along the northeastern corridor, in central Middlesex County, and along the Delaware River in the southwest.

Can Open Data Map New Jersey's Warehouses? A Lesson in Data Gaps and Spatial Bias

Supports: Can Open Data Map New Jersey's Warehouses? A Lesson in Data Gaps and Spatial Bias

Build an as-of-date warehouse dataset from OpenStreetMap, check its completeness, and test whether warehouse exposure is associated with community characteristics after accounting for spatial dependence.

Produces: 2 figures and 2 tables

  • Python
  • esda
  • GeoPandas
  • matplotlib
  • NumPy
  • pandas
  • Shapely
  • spreg
  • statsmodels
Grouped bar chart of the percent of neighborhoods by number of serious hazards out of 12, comparing overburdened communities with other neighborhoods. About 55% of other neighborhoods have none, while overburdened communities are spread out, with about 20% having seven or more.

Piling Up: How Many Serious Hazards Does Each Neighborhood Face at Once?

Supports: Piling Up: How Many Serious Hazards Does Each Neighborhood Face at Once?

Count how many serious hazards each neighborhood faces and test whether overburdened communities face more, within the same county.

Produces: 2 figures and 1 table

  • Python
  • GeoPandas
  • matplotlib
  • NumPy
  • pandas
  • statsmodels
Horizontal stacked bar chart showing, for each of 12 stressors, how many overburdened communities would lose or gain adverse status if that stressor were dropped. Totals range from about 94 for PM2.5 to 157 for emergency planning sites, with most changes being losses.

The Supreme Court Takes the Case: Stress-Testing New Jersey's Stressor Test

Supports: The Supreme Court Takes the Case: Stress-Testing New Jersey's Stressor Test

Test how sensitive NJDEP's "adverse cumulative stressors" determinations are to individual stressors and to measurement error.

Produces: 2 figures and 2 tables

  • Python
  • GeoPandas
  • matplotlib
  • NumPy
  • pandas
Bar chart of the percent of New Jersey schools within 1,000 feet of a limited-access highway, by quartile of students eligible for free or reduced-price lunch. The share rises from 2.3% in the lowest quartile to 5.2%, 7.7%, and 10.9% in the highest.

Back to School Next to the Highway

Supports: Back to School Next to the Highway

Measure how many New Jersey students attend school near limited-access highways and whether this differs by school poverty.

Produces: 2 figures and 1 table

  • Python
  • GeoPandas
  • matplotlib
  • NumPy
  • pandas
  • statsmodels
Map of New Jersey showing scrap metal facilities as black dots over a gray kernel density surface and orange overburdened communities. The densest clusters are in the northeastern Newark-Elizabeth-Jersey City corridor, with smaller hot spots near Trenton and along the Camden waterfront.

The Scrap Yard Next Door: Mapping New Jersey's Metal Recyclers

Supports: The Scrap Yard Next Door: Mapping New Jersey's Metal Recyclers

Map where scrap metal facilities cluster, and compare how close OBC and other residents live to them.

Produces: 2 figures

  • Python
  • GeoPandas
  • matplotlib
  • NumPy
  • pandas
  • SciPy
Paired bar chart for 12 municipalities comparing each one's percent of most-vulnerable residents with its percent of award dollars. Trenton gets about 35% of dollars with about 5% of residents and Camden about 22% with about 3%, while Paterson gets 1% with about 11% and several others, including Passaic and Jersey City, get none.

Did the Heat Grants Reach the Hottest Places?

Supports: Did the Heat Grants Reach the Hottest Places?

Compare where heat-island grant dollars went with where heat vulnerability is concentrated.

Produces: 2 figures and 1 table

  • Python
  • GeoPandas
  • matplotlib
  • NumPy
  • pandas
  • SciPy
Scatter plot of each block group's surface temperature anomaly against the average of its neighbors, both in degrees Celsius. The points form a tight upward line from about -8 to 17, showing that hot block groups have hot neighbors (global Moran's I of 0.84).

The July Heat Wave: Mapping New Jersey's Heat Hot Spots

Supports: The July Heat Wave: Mapping New Jersey's Heat Hot Spots

Test whether heat clusters spatially and identify statistically significant hot spots.

Produces: 2 figures and 2 tables

  • Python
  • esda
  • GeoPandas
  • matplotlib
  • NumPy
  • pandas
  • rioxarray
  • SciPy
Bar chart of the percent of neighborhoods in the top fifth for nearby chemical-hazard facilities, grouped by share of people of color. The share rises from about 7% in the fewest group to about 35% in the most, crossing the 20% even-spread line at the fourth group.

One Bad Day: Who Lives Near New Jersey's High-Risk Chemical Facilities?

Supports: One Bad Day: Who Lives Near New Jersey's High-Risk Chemical Facilities?

Test whether overburdened communities are more likely to be among New Jersey's most chemical-hazard-dense neighborhoods, and how confident we can be.

Produces: 2 figures and 1 table

  • Python
  • GeoPandas
  • matplotlib
  • NumPy
  • pandas
  • statsmodels
Two panels covering 2015 to 2025. On the left, lines for each monitor's 4th-highest daily 8-hour ozone fall from the mid-70s toward 60 around 2020 and then rise again, crossing the 70 ppb standard line. On the right, bars of days any monitor exceeded 70 ppb drop from 32 in 2015 to 5 in 2020, then rebound to 17, 20 and 16 in 2023 to 2025.

A Decade of Ozone: New Jersey's Progress Has Stalled

Supports: A Decade of Ozone: New Jersey's Progress Has Stalled

Measure ozone trends at New Jersey monitors from 2015 to 2025, and test whether trends differ in OBCs.

Produces: 1 figure and 1 table

  • Python
  • GeoPandas
  • matplotlib
  • NumPy
  • pandas
  • SciPy
  • statsmodels
Bar chart of the percent of residents living within 1 mile of a toxic release facility: 35% in overburdened communities versus 10% in other neighborhoods.

Living Next to Industry: Who Lives Within a Mile of a Toxic-Release Facility?

Supports: Living Next to Industry: Who Lives Within a Mile of a Toxic-Release Facility?

Test whether overburdened communities and communities of color are more likely to live within a mile of a TRI facility, comparing neighborhoods within the same county.

Produces: 2 figures and 1 table

  • Python
  • GeoPandas
  • matplotlib
  • NumPy
  • pandas
  • SciPy
  • statsmodels
Horizontal bar chart of health-based water violations by type. The Revised Total Coliform Rule (about 440) and Lead and Copper Rule (about 335) dominate, while nitrate, TTHM, gross alpha, radium and surface water rules are each under 45.

Is the Water Safe? Health-Based Drinking Water Violations in New Jersey

Supports: Is the Water Safe? Health-Based Drinking Water Violations in New Jersey

Test whether health-based violations are more frequent in systems serving overburdened communities, using an appropriate model for count data.

Produces: 2 figures and 2 tables

  • Python
  • GeoPandas
  • matplotlib
  • NumPy
  • pandas
  • statsmodels
Overlapping histogram of the percent of residents at each level of modeled cancer risk for overburdened and other communities. Nearly everyone falls at about 30 per million, but a larger share of other residents are in the lower group near 20 (about 17% versus 5%), and only overburdened residents appear near 40.

Earth Day 2026: The Air Toxics Picture in New Jersey

Supports: Earth Day 2026: The Air Toxics Picture in New Jersey

Compare modeled air toxics cancer risk between OBC and non-OBC residents, with uncertainty, and identify which sources drive the gap.

Produces: 2 figures and 1 table

  • Python
  • GeoPandas
  • matplotlib
  • NumPy
  • pandas
Single stacked bar showing the materials of 1.32 million service lines in 41 surveyed New Jersey water systems: 7% lead, 2% galvanized needing replacement, 54% unknown and 36% non-lead.

Five Years Left: Can New Jersey Replace Its Lead Pipes by 2031?

Supports: Five Years Left: Can New Jersey Replace Its Lead Pipes by 2031?

Describe New Jersey's service line inventory and test whether lead-line share relates to OBC share, with honest uncertainty.

Produces: 2 figures and 1 table

  • Python
  • GeoPandas
  • matplotlib
  • NumPy
  • pandas
  • SciPy
Bar chart of the combined stressor total at five distances from the PVSC treatment plant, from 0-1 mile to 5-10 miles. Every bar (about 17 to 20) is above the New Jersey average of 15.0, with only a slight decline with distance.

The Ironbound's Fourth Power Plant: A Court Hits Pause

Supports: The Ironbound's Fourth Power Plant: A Court Hits Pause

Summarize cumulative burden in rings around PVSC.

Produces: 2 figures and 1 table

  • Python
  • GeoPandas
  • matplotlib
  • NumPy
  • pandas
Bar chart of how many times higher contaminated-site density is in overburdened than other neighborhoods: 3.39 times in the raw comparison, 2.03 times within the same county, and 1.28 times within the same county and at the same pavement level, all above the line at 1.

Five Years After the EJ Law: Contaminated Sites Next Door

Supports: Five Years After the EJ Law: Contaminated Sites Next Door

Test whether contaminated-site density is higher in overburdened communities even after comparing neighborhoods within the same county and with similar development.

Produces: 2 figures and 2 tables

  • Python
  • GeoPandas
  • matplotlib
  • NumPy
  • pandas
  • statsmodels
Two box plots comparing overburdened and other block groups. Median tree canopy is about 20% in overburdened block groups versus about 39% in others, while median impervious surface is about 62% versus about 34%.

Planting Shade: Where New Jersey's New Heat Program Could Do the Most Good

Supports: Planting Shade: Where New Jersey's New Heat Program Could Do the Most Good

Compare shade and pavement between OBCs and other block groups, then separate their effects on municipal heat.

Produces: 2 figures and 1 table

  • Python
  • GeoPandas
  • matplotlib
  • NumPy
  • pandas
  • statsmodels
Map of New Jersey showing 55 fuel-burning power plants as red dots sized by megawatts over orange overburdened communities. The largest dots cluster in the dense orange northeast, with others along the Delaware River in the southwest.

The Plants Behind the Bill: Who Lives Next to New Jersey's Power Plants?

Supports: The Plants Behind the Bill: Who Lives Next to New Jersey's Power Plants?

Measure how close New Jersey's fuel-burning power plants are to overburdened communities and communities of color.

Produces: 2 figures and 1 table

  • Python
  • GeoPandas
  • matplotlib
  • NumPy
  • pandas
  • SciPy
Horizontal bar chart of the rank correlation between 13 pollution indicators and the share of people of color in New Jersey neighborhoods. Twelve are positive, from 0.59 for nearby traffic to 0.21 for lead paint, while drinking water non-compliance is slightly negative at -0.05.

The Map EPA Took Down: What EJScreen's Last Data Showed About New Jersey

Supports: The Map EPA Took Down: What EJScreen's Last Data Showed About New Jersey

Measure how strongly each EJScreen indicator rises with the share of people of color in New Jersey neighborhoods.

Produces: 2 figures and 1 table

  • Python
  • GeoPandas
  • matplotlib
  • NumPy
  • pandas
  • SciPy
Bar chart of the median number of residents per acre of open space within a quarter mile: 69 in overburdened communities versus 19 in other neighborhoods.

Room to Breathe: Who Has Parks and Open Space Nearby?

Supports: Room to Breathe: Who Has Parks and Open Space Nearby?

Compare access to parks and open space in overburdened and other neighborhoods.

Produces: 2 figures and 2 tables

  • Python
  • GeoPandas
  • matplotlib
  • NumPy
  • pandas
Bar chart of the percent of residents living in neighborhoods where most homes were built before 1950: 24% in overburdened communities versus 11% in other neighborhoods.

Old Homes, Old Paint: Who Lives Where Lead Paint Is Most Likely?

Supports: Old Homes, Old Paint: Who Lives Where Lead Paint Is Most Likely?

Compare how much old housing (built before 1950) overburdened communities and other neighborhoods have.

Produces: 2 figures and 2 tables

  • Python
  • GeoPandas
  • matplotlib
  • pandas
Horizontal bar chart of the percent of each New Jersey county's residents living in an overburdened community, ranging from about 90% in Hudson County and 80% in Essex down to about 7% in Hunterdon and 6% in Sussex.

Environmental Injustice Is Real in New Jersey: Introducing This Project

Supports: Environmental Injustice Is Real in New Jersey: Introducing This Project

Use NJDEP's official data to count overburdened communities and compare their pollution burdens with other neighborhoods.

Produces: 3 figures and 2 tables

  • Python
  • GeoPandas
  • matplotlib
  • pandas