Methodology & sources
Data sources
- Demographic, social, economic, and housing variables: U.S. Census Bureau, American Community Survey 5-year estimates for 2020–2024, fetched via the Census Reporter API (a no-key cache of Census API data maintained by the Census Reporter project).
- 2020 Decennial Census counts (race, age, total population): Pulled via the Census API from the 2020 PL 94-171 redistricting file (tract-level
P1_001Ntotal population) and via an IPUMS NHGIS extract of 2020 DHC tables P9 (Hispanic × Race) and P12 (Sex by Age). These are 100% counts, not survey estimates — no margin of error. They show up alongside the ACS-equivalent variables (e.g., "White non-Hispanic (2020 count)" sits next to "White non-Hispanic" from ACS), so you can compare and use the more precise count where 5-year-stale data is acceptable. - Tract geometry: NYC Department of City Planning, 2020 Census Tracts (shoreline-clipped). Polygons are used as published in DCP's GeoJSON export, which averages about 75 vertices per tract; no further simplification is applied.
- Crime rates: NYC Open Data, NYPD Complaint Data — Historic plus Year-To-Date, restricted to the seven major felonies and aggregated to tracts by point-in-polygon. Window is a rolling 12 months ending on the most recent date present in the data. Complaints that NYPD places at a precinct station house are left out, and complaints on a boundary are shared (see the crime rate details below).
- Election results: NYC Board of Elections citywide ED-level CSVs (vote.nyc) for the 2020 and 2024 general elections (president); pre-aggregated mayoral results from nyc-election-archive for 2021 and 2025 mayoral generals. ED polygons aggregated to 2020 census tracts via centroid point-in-polygon.
- Rent-stabilized units: JustFix.nyc's annual compilation of NYC Department of Finance tax-bill scrapes — buildings carrying a "rent-stabilization fee" line item on their property tax bill. BBLs joined to lat/lon via NYC DCP PLUTO, then aggregated to 2020 census tracts by point-in-polygon. Citywide totals (~994K units in ~42K buildings on the 2024 tax-bill cycle) are in line with the roughly one million stabilized units the Rent Guidelines Board and the Independent Budget Office report.
- Public-school enrollment by school location: NYC DOE, Demographic Snapshot (c7ru-d68s) for K-12 grade counts plus School Locations (wg9x-4ke6) for lat/lon. Latest year per school (most recently 2021-22). Includes DOE-managed charter schools; excludes private and parochial schools.
Geography
All five New York City boroughs at the 2020 census tract level — 2,325 tracts in total. (The ACS file carries 2,327; two have no polygon in DCP's shoreline-clipped set and are dropped.) A tract is a small statistical area that typically contains 1,200–8,000 people. Tract boundaries are stable enough to compare across years but small enough to surface meaningful neighborhood-scale differences.
The "Borough" and "NTA" (Neighborhood Tabulation Area) labels in the detail panel come from NYC DCP's official 2020 designations. Tracts that fall in parks, cemeteries, airports, or industrial areas may show small or zero population.
Neighborhoods (NTAs)
The default view aggregates tracts into NYC DCP's 262 Neighborhood Tabulation Areas: 197 residential NTAs plus 65 that DCP classifies as parks, cemeteries, airports, industrial or correctional areas. Each tract belongs to exactly one NTA.
- Counts and shares are recomputed from the sum of the underlying ACS cells across member tracts, so an NTA poverty rate is total poor ÷ total poverty universe, not an average of tract rates. MOEs for the summed cells use √Σmoe², then the same proportion formula as for tracts. Combining roughly nine tracts cuts a typical relative MOE to about a third of the tract-level figure.
- Medians cannot be summed or averaged. NTA medians are interpolated from the pooled distribution behind each one: the bracket counts are added up across member tracts, the bracket holding the middle household (or person, or unit) is found, and the median is placed within that bracket by linear interpolation. The distributions are B19001 for household income, B01001 (both sexes) for age, B25063 for gross rent, B25075 for home value and B25070 for rent burden. This is the method NYC City Planning uses for its own NTA profiles. Checked against City Planning's published 2020–2024 NTA tables, the results match to within $2 for income, rent and home value and 0.1 year for age in all 197 residential NTAs. No ± is shown for these interpolated medians.
- Open-ended top brackets. When the middle household falls in a bracket with no upper limit, the median is known only to be at least that bracket's floor, and the map prints it that way: "$200,000+" for income, "$3,500+" for rent, "$2,000,000+" for home value. Tribeca-Civic Center and Upper East Side-Carnegie Hill are top-coded on income. Tract medians come straight from the Census Bureau, which top-codes them at "$250,000+", "$3,500+", "$2,000,000+" and "50%+" (rent burden) and bottom-codes a few; the map shows those the same way. For sorting and coloring, a top-coded value counts as its floor.
- Average household size is exact at the NTA level: tract averages weighted by households add up to people in households ÷ households.
- 2020 Decennial counts, election votes, rent-stabilized units, school enrollment and crime counts are summed across member tracts. Crime rates are suppressed for NTAs with fewer than 500 residents.
- Non-residential areas. City Planning classifies 65 NTAs as parks, cemeteries, airports, military installations, jails or industrial zones. Most have no residents. The few people counted in the others live in places like Rikers Island, Randall's Island and Fort Hamilton, and their shares swing widely (Miller Field, with 75 residents, came out 85% Hispanic). These NTAs, and the tracts inside them, keep their counts (population, households, votes, units) but show no shares, medians or rates, so they do not set the color scales for residential neighborhoods. They are drawn in a paler gray than "no data."
Change, October 2026: earlier versions of this map averaged the tract medians, weighted by households or units. That approximation was off by a median of about $1,900 on NTA household income and by as much as $28,000 (Old Astoria-Hallets Point showed $112,417; City Planning's figure is $84,443). The worst rent gap was $413 (Midtown-Times Square) and the worst home-value gap $302,000 (Hunts Point showed $635,400; City Planning has $333,332). Tribeca showed $229,813, a figure the brackets cannot support; its median is above $200,000 and can't be pinned down further. The non-residential NTAs were also included in the color scales. Both are fixed.
How values are calculated
Every variable in the map either comes directly from an ACS estimate (medians and totals) or is computed as a share, using the denominator the Census Bureau publishes alongside that table. For example:
- Poverty rate =
B17001_002(residents below poverty) ÷B17001_001(residents for whom poverty status was determined). The denominator excludes people in institutional group quarters, college dormitories and military barracks, which is why the rate is not divided by total population. - Share Hispanic or Latino =
B03002_012÷B03002_001. The race/ethnicity shares come from table B03002, which separates Hispanic ethnicity from race so the four categories shown (white non-Hispanic, Black non-Hispanic, Asian non-Hispanic, Hispanic any race) do not double-count. - Population density = total population ÷ land area in square miles. Land area is computed from the shape area of the shoreline-clipped tract polygon (NAD83 NY State Plane Long Island, ftUS).
Full variable list
| Field | Source table / formula |
|---|---|
| Total population | B01003_001 |
| Population density | B01003_001 ÷ tract land area (sq mi) |
| Median age | B01002_001 |
| Share under 18 | Σ B01001 male+female under-18 cells ÷ B01003_001 |
| Share 65 and over | Σ B01001 male+female 65+ cells ÷ B01003_001 |
| White, Black, Asian (non-Hispanic), Hispanic | B03002_003 / 004 / 006 / 012 ÷ B03002_001 |
| Median household income | B19013_001 (tracts); interpolated from B19001 (NTAs) |
| Household income bands | Sums of B19001 cells ÷ B19001_001 |
| Poverty rate | B17001_002 ÷ B17001_001 |
| Receiving SNAP | B22010_002 ÷ B22010_001 |
| Households / avg household size | B11016_001 / B25010_001 |
| Owner-occupied homes | B25003_002 ÷ B25003_001 |
| Median gross rent / home value | B25064_001 / B25077_001 (tracts); interpolated from B25063 / B25075 (NTAs) |
| Median rent burden | B25071_001, median gross rent as % of household income (tracts); interpolated from B25070 (NTAs) |
| Bachelor's+ / HS only | Σ B15003_022–025 / Σ B15003_017–018 ÷ B15003_001 |
| Foreign-born | B05002_013 ÷ B05002_001 |
| Not a U.S. citizen | B05002_021 ÷ B05002_001 |
| Non-English at home | (C16001_001 − C16001_002) ÷ C16001_001 |
| In labor force (16+) | B23025_002 ÷ B23025_001 |
| Commute mode shares | B08301_010 / 019 / 021 ÷ B08301_001 |
| Veterans (18+) | B21001_002 ÷ B21001_001 |
| Unemployment rate | B23025_005 ÷ B23025_003 (BLS-standard: unemployed ÷ civilian labor force, not ÷ total labor force) |
| No home internet | B28002_013 ÷ B28002_001 (no subscription and no other internet access at the residence; households with free access but no subscription are in B28002_012 and are not counted here) |
| Households with no vehicle / 2+ vehicles | B08201_002 / Σ B08201_004-006 ÷ B08201_001 |
| Average vehicles per household | Σ (bucket midpoint × bucket count) ÷ B08201_001 (4+ bucket treated as 4.5) |
| Homeowners / renters without a vehicle | B25044_003 ÷ B25044_002 / B25044_010 ÷ B25044_009 |
| K-12 students in public / private school (by residence) | Σ B14002 K-12 public (or private) male + female cells ÷ total K-12 students. Tracks where the students live. |
| Public-school K-12 enrolled (by school location) | Σ DOE Demographic Snapshot enrollment by school (latest year), aggregated to the tract containing each school's address. Tracks where the schools are. |
| Major-felony rate | (Σ NYPD complaints with KY_CD ∈ {101, 104, 105, 106, 107, 109, 110}) ÷ population × 1,000 |
| Violent-crime rate | (Σ NYPD complaints with KY_CD ∈ {101, 104, 105, 106}) ÷ population × 1,000 |
| Property-crime rate | (Σ NYPD complaints with KY_CD ∈ {107, 109, 110}) ÷ population × 1,000 |
Election details
Vote shares are reported as the candidate's share of major-candidate votes — not of all ballots cast. For the presidential races and the 2021 mayoral race, this means D ÷ (D + R), so the values are directly comparable across years even when third-party candidates differ. For 2025 mayoral, the denominator is Mamdani + Cuomo + Sliwa (the three major candidates); minor candidates are not counted in the denominator.
Each candidate's vote total includes all of their ballot lines, because cross-endorsed lines are votes for the same person. Biden 2020 and Harris 2024: Democratic + Working Families. Trump 2020 and 2024: Republican + Conservative. Adams 2021: Democratic. Sliwa 2021: Republican + Independent (the Conservative line that year went to Bill Pepitone, who is not counted). Mamdani 2025: Democratic + Working Families. Cuomo 2025: Fight and Deliver. Sliwa 2025: Republican + Protect Animals.
NYC redrew Election District boundaries after the 2020 redistricting cycle, so 2020/2021 results use the pre-redistricting ED geometry (5,697 EDs with polygons) and 2024/2025 results use the post-redistricting geometry (4,069 EDs). Both are aggregated to the same 2020 census tract boundaries. Because EDs are typically smaller than tracts (about 2.5 EDs per tract for 2020–21 and 1.75 for 2024–25), the centroid-based join is reliable.
Votes cast in EDs that have no polygon in the archive, or whose centroid falls outside every tract, are dropped. Against the certified citywide totals, the tract sums capture 99.9% of major-candidate votes for the 2020 presidential and 2021 mayoral races, 99.3% for the 2025 mayoral race and 98.7% for the 2024 presidential race; the 2024 gap is EDs in the Board of Elections file that are missing from the post-redistricting polygon set.
Tracts with fewer than 25 total major-candidate votes for a given election are suppressed (shown as "no data") for that election, because vote shares from tiny samples are noisy.
Rent-stabilized units — how the unit count is derived
NYC's official rent-stabilization registry, maintained by the state's Division of Housing and Community Renewal (DHCR), does not publish unit counts per building. The annual Rent Guidelines Board lists released by NYC RGB are PDFs of building addresses only — no unit data.
The numbers on this map come from a workaround that exploits NYC's tax system. The Department of Finance charges every rent-stabilized building an annual per-unit "Rent Stabilization Fee" on its property tax bill ($20 per unit since July 1, 2019; $10 before that). The fee line item appears on the property's annual DOF Statement of Account PDF. Divide that fee by the per-unit rate and you've reverse-engineered the registered unit count for that building. The JustFix.nyc team's nyc-doffer project scrapes every NYC tax lot's Statement of Account PDFs annually, extracts the fee, and publishes per-BBL unit counts back to 2018. That's what's aggregated to tracts here.
What the unit counts actually mean and don't mean:
- They are "billed" units, not "currently occupied stabilized" units. A building paying the fee for 50 units may have fewer than 50 still under active stabilization — vacancy decontrol, deregistration in process, and other transitions show up in DOF rolls with a lag.
- Chronic landlord under-registration probably means the true universe is higher. Tenant advocates have long argued that many stabilized units never get registered with HCR (the fee is small enough that some landlords skip it). The map's number is the billed count, which is a floor on the real count, not a ceiling.
- The citywide total cross-checks reasonably. Tract sums for the 2024 tax-bill cycle add to ~994,000 units across ~42,400 buildings. The Rent Guidelines Board describes the stabilized stock as "just less than one million" units and the city's Independent Budget Office puts it at approximately one million; the tax-bill count sits in that range. HCR does not publish a matching citywide unit total, so a precise reconciliation is not possible.
- For the building count specifically, the on-tax-bill list and the DHCR registration list are not identical universes. A handful of buildings appear in one but not the other; the differences are small enough that the tract-level pattern is reliable.
Crime rate details
NYPD's "seven major felonies" (sometimes called Index 7) are the historical FBI Part I categories the department reports for CompStat. We split them into the two standard buckets:
- Violent — murder & non-negligent manslaughter (KY_CD 101), rape (104), robbery (105), felony assault (106).
- Property — burglary (107), grand larceny (109), grand larceny of motor vehicle (110).
- Major-felony total — the sum of the two.
Each complaint is filed to the date the incident was reported to police (RPT_DT), not the date it allegedly occurred — that's the field NYPD uses for its own performance reporting, and the only one that's reliably populated.
Station houses. NYPD places complaints it cannot geocode, and nearly every rape complaint (to protect victims), at the station house of the precinct where they happened: in the first half of 2026, 1,003 of 1,065 rape complaints sat within 130 meters of their precinct's station house. These complaints have no known street location, so the map leaves them out: any complaint within 140 meters of its own precinct's station house is not counted (station addresses from each precinct's NYPD page, geocoded with NYC Planning's GeoSearch, plus the 40th Precinct's former building at 257 Alexander Avenue, where NYPD still places its records). That is 4,408 of the 118,463 complaints in the July 2025–June 2026 window, 3,075 of them violent, so in practice the violent-crime rate counts very few rapes. No complaint is assigned to a place by its precinct alone. Left in place, they would pile into the one tract holding the station house (some carried as many as 95 extra violent felonies a year). The 140-meter radius comes from 2024 arrests: in 75 of 76 precincts, the largest stack of arrests spanning many offense types sits within 140 meters of the geocoded station house.
Records on a boundary. NYPD places each complaint at an intersection or the middle of a street segment, and most tract and neighborhood boundaries run down street centerlines. A complaint within 10 meters of a tract boundary is shared equally among the tracts that meet there (44,518 of the 114,055 street-located complaints), so counts can be fractional.
Complaints are placed on the tract map by point-in-polygon on the NYPD-reported latitude/longitude. The denominator is total population from the same ACS table used elsewhere on this page (B01003_001), so the rate is per 1,000 residents, not per 1,000 daytime population or per 1,000 visitors. Tracts with fewer than 200 residents (103 tracts: parks, cemeteries, airports, transit yards, industrial areas) are shown as "no data" on the rate maps because the denominator is too small to be meaningful — a single felony in a 12-person tract would produce an 83-per-1,000 rate. Tracts inside NTAs that DCP classifies as non-residential (65 of the 262) also get no rate. Tracts and NTAs whose rate exceeds 100 per 1,000 residents keep their rate but carry a daytime-population caveat in the detail panel, since such rates almost always reflect crimes against commuters and visitors.
The "rolling 12 months" window spans the most recent 365 days for which combined data are available. Because the Historic dataset is updated quarterly and the YTD dataset daily, this can shift forward each time the build script is re-run.
Margins of error and reliability
ACS is a sample, not a count. Every cell estimate comes with a margin of error (MOE) at 90% confidence. For tracts with small populations or small subgroups, the MOE can be a substantial fraction of the estimate itself — sometimes larger than the estimate. We surface this in three places:
- Tooltips show "± X" alongside the value, with a colored badge indicating three reliability tiers that follow a common convention (used by Esri and many local agencies; the Census Bureau itself publishes no official cutoffs): reliable (MOE ≤ 20% of estimate; CV ≤ 0.12), use with caution (20–66%; CV 0.12–0.40), and unreliable (>66%; CV > 0.40).
- The tract-detail panel shows ± for every variable that has a propagated MOE.
- A "Fade unreliable estimates" checkbox in the sidebar adds a 25% opacity overlay and dashed border to any tract whose value falls in the "unreliable" tier — so users can see uncertainty visually without losing the underlying data.
MOEs for derived shares are propagated using the Census Bureau's standard formulas from the ACS General Handbook (chapter 8, "Calculating Measures of Error for Derived Estimates"): moe(p) = √[moe(x)² − p² · moe(y)²] / y for proportions where the numerator is a subset of the denominator (with a fall-back to the independent-ratio formula when the radicand is negative). Sum-of-cells MOEs use √Σmoe². Tract medians use the published MOE directly; interpolated NTA medians carry no ±.
The undocumented-population proxy
The U.S. Census Bureau does not ask about immigration status, so there is no direct tract-level count of undocumented residents. Every published "undocumented by tract" figure is a model output, not a measurement. The closest publicly available variable that gets at this population without modeling assumptions is B05002_021 — "Foreign-born, not a U.S. citizen." We surface it as "Not a U.S. citizen."
This variable over-counts the undocumented population because it also includes:
- Lawful permanent residents (green-card holders) who haven't naturalized
- Visa holders — students (F-1), workers (H-1B, L-1, O-1, etc.), exchange visitors (J-1), and others
- Refugees, asylees, and other humanitarian admissions
- TPS (Temporary Protected Status) recipients
- DACA recipients (technically without status but with deferred action)
Calibrating it to undocumented requires a multiplier. Nationally, Pew Research Center estimates 14 million unauthorized immigrants in 2023, 27% of the foreign-born population. For New York City, the Center for Migration Studies estimates 535,000 undocumented residents in 2023, 15% of the city's 3.1 million foreign-born and roughly two-fifths of its non-citizens (CMS counts 820,400 legal non-citizens alongside the 535,000 undocumented). The share varies by country of origin: CMS finds Mexico is the largest source of undocumented New Yorkers, China second and the Dominican Republic fourth, so the multiplier runs higher where those groups concentrate and lower where non-citizens are mostly green-card holders or visa holders. A tract with 1,000 "Not a U.S. citizen" residents probably contains something like 400 undocumented residents, with wide neighborhood-level variation.
This map does not multiply by that factor — we show the raw proxy and ask the reader to understand it as such. CMS publishes more authoritative estimates at sub-city geographies (Community District level); when this map grows a Community District aggregation, those will be layered in directly.
What is not yet here
- NYSED non-public (private + parochial) school rosters. Available only as Microsoft Access files; requires
mdbtoolsor equivalent to parse. Public-school enrollment from the NYC DOE Demographic Snapshot is in. - LEHD LODES jobs/commute data. Administrative payroll records — better than ACS B23025 for employment counts. A natural next addition.
- Hispanic country-of-origin breakdown at tract level (Mexican, Dominican, Salvadoran, etc.). The 2020 Detailed DHC-A publishes tract-level counts for detailed Hispanic groups, but only where a group's population in the tract clears the Bureau's disclosure thresholds, so coverage is patchy. ACS B03001 has it everywhere, but with high margins of error at tract level.
Color scales
The map offers two binning options for every variable:
- Quantile (default): seven equally populated bins so each color contains roughly the same number of areas. This emphasizes relative ranking across the city. When many areas share a value (zero rent-stabilized units, say, or a top-coded median), cut points repeat; the map drops the repeats, so a variable can show fewer than seven classes, and a value that falls exactly on a cut point goes in the class below it.
- Linear: a continuous gradient from the lowest to highest value, which preserves the absolute spacing between tracts but can be visually dominated by outliers.
Each thematic group uses a different single-hue palette to make the active variable immediately recognizable.
Two variables. Each variable is split into thirds (low, middle, high) across the areas that have both values, and the 3×3 palette colors each area by its pair of thirds. The legend labels the two cut points on each axis and lists how many areas fall in each third; ties can make the thirds uneven. The correlation shown is Pearson's r across the same areas, each counted once regardless of population. The preset explanations were checked against the neighborhood data; the places they name sit in the cells described at the neighborhood level, and the direction of each relationship holds at the tract level, where most are weaker.
Summary statistics. The lowest, middle, average and highest values in the sidebar describe the areas on the map, each counted once. They are not citywide figures: the average of 197 neighborhood poverty rates is not the city's poverty rate.
Known limits
- Tract-level estimates are 5-year rolling averages (2020–2024). Conditions in any single year may differ.
- Tracts with very small denominators (e.g., few owner-occupied units) will show missing values or unstable percentages. Missing values are shown in gray.
- "No data" can mean the Census Bureau suppressed an estimate for confidentiality, the denominator was zero, or the tract has no relevant population.
- Race/ethnicity categories follow Census Bureau conventions and do not represent every possible identity. People of "two or more races" and "some other race" are not shown separately here.
- Income brackets reflect dollar values not adjusted for inflation or cost-of-living differences across tracts.
- The "Public-school K-12 enrolled (by school location)" and "Public K-12 schools in tract" variables come from NYC DOE roster data — actual student counts, not survey estimates — and are far more accurate than the ACS-based numbers. But they answer a different question: where the schools are, not where the students live. Tracts with no school in them score zero on these regardless of how many school-age kids live there. The ACS "by residence" numbers in the same group answer the residence question. Together they triangulate.
- NYSED non-public (private + parochial) enrollment is not yet integrated. The data exists at data.nysed.gov but is published only as Microsoft Access (.accdb) files, which require additional tooling (mdbtools or Office) to parse. Wiring it up is a known TODO.
- School enrollment is split only into "public" and "private." The Census Bureau folds parochial (Catholic, Jewish day, Islamic, and other religious) schools into the "private" bucket, and there is no tract-level public dataset that separates parochial from secular private. Specialized public schools, charters, and gifted-and-talented programs are all counted as "public."
- K-12 enrollment in B14002 is by student residence, not by school location, so a tract's "% in private school" reflects where the families live, not where the schools are.
- Vehicle counts come from a survey question about vehicles "kept at home"; company cars and ride-share vehicles a household uses but doesn't own are not counted.
Reproducing this map
The Python build script (build.py) fetches all ACS variables from Census Reporter (caching the raw responses in cache/; pass --refresh to re-download), joins them to NYC DCP tract geometry, computes the derived shares, aggregates tracts to NTAs and writes tracts.geojson, ntas.geojson and variables.json into the docs/ folder. The page is plain HTML/JS — Leaflet for the map, d3 for color scales, no build step. Source is available on GitHub.