Skip to content

F-022: Household Income Data

Status: Done · Priority: P2 · Updated: Mar 6, 2026

Summary

Display household and individual income data on the platform. Census tract choropleth map showing median household income, 2016→2021 income change visualization, and FSA-level income trends from CRA tax data. Listing detail pages show the census tract's median household income for the listing's location.

Requirements

  • [x] Census tract boundary shapefile import + GeoJSON conversion (2021 + 2016)
  • [x] Census tract income data import (2021 + 2016 census)
  • [x] Choropleth map page (/income-map) with 2021 income + 2016→2021 change modes
  • [x] Per-listing income display via nearest census tract lookup
  • [x] CRA individual tax data by FSA (2015–2021, annual)
  • [x] FSA income trends page (/income-map/trends) with sparklines
  • [x] Historical income snapshots table (income_snapshots)
  • [x] API endpoints for income data + trends + comparisons
  • [x] Bilingual (FR/EN)
  • [x] PostGIS extension enabled
  • [x] Income distribution brackets (StatCan 98-10-0055, 19 brackets, 2016+2021)
  • [x] Tabbed income page (Census Map, Distribution, Rent Trends)
  • [x] recharts visualizations (bar charts, line charts)
  • [x] income_distributions + rent_snapshots tables
  • [x] API: /income/distribution, /income/rent-trends
  • [x] CMHC rental market data — auto-downloaded from StatCan Table 34-10-0133-01 (5,880 records, 205 geographies, 1987-2025)
  • [x] Dedicated /rent-data page with city selector, comparison mode (up to 5 cities), line chart, stats cards, sortable city table
  • [x] csv-parse dependency added to database package for CMHC CSV parser
  • [x] Nav header updated with "Rent Data" link
  • ISQ Quebec income by MRC — dropped (removed ISQ parser, MRC tab, mrc-income API endpoint, xlsx dependency)
  • [ ] Affordability heatmap page (rent vs income by area — separate future feature)

Data Sources

In Database

SourceTypeGeographyYearsRecords
Census 2021 (98-10-0058-01)Household incomeCensus tract2021986 CTs
Census 2016 (Census Profile)Household incomeCensus tract2016951 CTs
CRA Individual Tax StatsIndividual incomeFSA2015–20211,945 (278 FSAs × 7 years)
CT Boundaries 2021Shapefile→GeoJSONCensus tract20211,004 CTs
CT Boundaries 2016Shapefile→GeoJSONCensus tract2016downloaded, not imported

Income Type Clarification

Census data = household income. Total income of all persons in a household combined. Range: $23k (student areas like Quartier Latin) to $360k (Westmount). Montreal CMA median: ~$76k.

CRA data = individual income. Average total income per individual tax filer. Range: $7.5k (tiny rural FSAs) to $400k. Not directly comparable to census household income.

Both are clearly labeled in the UI to avoid confusion.

Auto-Downloaded Data

CMHC Rental Market Survey (StatCan Table 34-10-0133-01)

CMHC rent data is now automatically downloaded from Statistics Canada's open data API during pnpm db:seed. No manual download required.

  • Source: StatCan Table 34-10-0133-01
  • Records: 5,880 across 205 Canadian geographies
  • Years: 1987–2025
  • Parser: packages/database/src/parsers/parse-cmhc-rent.ts (uses csv-parse)
  • Storage: rent_snapshots table

ISQ Quebec Income by MRC — Dropped

ISQ MRC data has been removed from the project. The ISQ parser, MRC tab on /income-map, /income/mrc-income API endpoint, and xlsx dependency have all been deleted.

Schema

census_tracts table

1,004 Montreal CMA census tracts with GeoJSON boundaries and 2021 income data.

ColumnTypeNotes
ct_uidtextunique, StatCan CT identifier (e.g., "4620001.00")
cma_uidtext"462" for Montreal
boundaryjsonbGeoJSON Polygon/MultiPolygon
latitude/longitudenumericcentroid
median_incomeintegerhousehold, before tax (2021)
median_income_after_taxintegerhousehold (2021)
total_householdsinteger(2021)
census_yearintegerdefault 2021

income_snapshots table

Historical income data across all sources and geographies.

ColumnTypeNotes
geo_typetext'ct', 'fsa', 'cma', 'mrc'
geo_codetextCT UID, FSA code, etc.
yearintegerdata year
sourcetext'census', 'cra', 'isq'
median_incomeintegerhousehold median (census only)
average_incomeintegerhousehold avg (census) or individual avg (CRA)
total_householdsintegercensus only
total_filersintegerCRA only

Unique index on (geo_type, geo_code, year, source).

postal_areas income columns

median_income, median_income_after_tax, average_income — currently set to CMA-level fallback ($76k). Not used for listing display (nearest CT is used instead).

API Endpoints

MethodPathDescription
GET/census-tractsAll CTs with boundaries + income (24h cache)
GET/census-tracts/:ctuidSingle CT detail
GET/income/snapshotsIncome snapshots, filterable by geo_type, source, year
GET/income/fsa-trendsCRA FSA income trends 2015–2021
GET/income/ct-comparison2016 vs 2021 CT income change (923 matched CTs)
GET/listings/:idNow includes areaMedianIncome via nearest CT centroid

Frontend Pages

/income-map — Choropleth Map

  • Mode toggle: 2021 Income (red→yellow→green by median household income) | 2016→2021 Change (red→green by % change, 923 CTs matched)
  • Hover: tooltip with CT name + income (or change %)
  • Click: popup with median income, after-tax, households, change %
  • Legend adapts to mode
  • Link to FSA Trends page
  • Table of ~278 Montreal FSAs with 2015–2021 average individual income
  • SVG sparkline per FSA showing 7-year trend
  • Sort by growth %, income level, or FSA code
  • Search/filter by FSA
  • Clear note: "individual tax filer income, not household"

Listing Detail — Area Income Card

  • Shows "Area Median Household Income: $X/yr" in sidebar
  • Uses nearest census tract by centroid distance (haversine, <5km)
  • Real variation: $46k–$109k+ across listings

Data Pipeline

Download Script

packages/database/scripts/download-census-data.sh — downloads:

  1. CT boundaries 2021 + 2016 (shapefiles from StatCan)
  2. Census income 2021 (Table 98-10-0058-01 CSV)
  3. Census Profile 2016 (full profile CSV, 165MB)
  4. CRA Individual Tax Stats by FSA (2015–2021, 7 CSV files)
  5. Instructions for manual CMHC + ISQ downloads

Parsers

  • packages/database/src/parsers/parse-census-income.ts — 2021 census CT income
  • packages/database/src/parsers/parse-census-2016-income.ts — 2016 Census Profile CT income
  • packages/database/src/parsers/parse-cra-fsa-income.ts — CRA FSA annual data (auto-detects dollars vs thousands format)

Boundary Conversion

packages/database/scripts/convert-ct-boundaries.ts — shapefile to GeoJSON, proj4 reprojection EPSG:3347→4326, filtered to Montreal CMA (ctUid starts with "462").

Seed

pnpm db:seed loads census tracts + all income snapshots (2,882 total records).

Discussion Notes

Mar 5, 2026

  • Researched 6 data sources for Montreal income data
  • Best primary: StatCan census CT income (household) + CRA tax stats (individual, annual)
  • CT boundaries available 2021 + 2016 from StatCan
  • CMHC rental market data requires manual portal export (1998–2025 available)
  • Decided: CT-level for map visualization, nearest-CT for per-listing income

Mar 6, 2026

  • Fixed listing income display: changed from $76k CMA fallback (useless) to nearest census tract lookup (real variation $46k–$109k)
  • Downloaded all auto-downloadable data: 2016 CT boundaries, 2016 Census Profile, CRA FSA 2015–2021
  • Built parsers for 2016 census + CRA FSA data, loaded 2,882 income snapshots into DB
  • Added income_snapshots table for multi-source historical data
  • Built 2016→2021 income change choropleth mode (923 CTs matched, avg ~20% growth)
  • Built FSA income trends page with sparklines (278 FSAs × 7 years)
  • Installed PostGIS extension for future spatial queries
  • Clarified: census = household income, CRA = individual income. Both labeled clearly.
  • Simplified listing detail: shows "Area Median Household Income: $X/yr" instead of affordability ratio
  • Affordability heatmap (rent vs income) deferred to separate future feature

Mar 6, 2026 (cont.)

  • CMHC data switched from manual portal download to auto-download from StatCan Table 34-10-0133-01. 5,880 records across 205 Canadian geographies, 1987-2025. Added csv-parse dependency to database package.
  • ISQ/MRC removed entirely: deleted ISQ parser, removed MRC tab from income-map, removed /income/mrc-income API endpoint, removed xlsx dependency, removed MRC translations.
  • Built dedicated /rent-data page with city selector, comparison mode (up to 5 cities), line chart, stats cards, sortable city table. Component: services/web/components/rent/rent-data-index.tsx.
  • Updated /income/rent-trends API to support optional geo_code query parameter for filtering by geography.
  • Added "Rent Data" link to header navigation.