Toronto Shelter System — current month at a glance
Interactive monthly snapshot of Toronto’s shelter system — actively homeless counts, flow metrics, and population group breakdowns for any month from January 2018 onward.
Author
Miriam Marling
data =FileAttachment("../data/shelter_flow.json").json()
MON = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]fmtN = d => d.toLocaleString()// Build month dropdown in descending order (most recent first).// Display: "March 2026" Return value: "2026-03-01" (the YYYY-MM-DD from DB)reportingMonthOptions = data.filter(d => d.population_group==="All Population").sort((a, b) => b.flow_date.localeCompare(a.flow_date)).map(d => {const [y, m] = d.flow_date.split("-").map(Number)return {label:newDate(y, m -1,15).toLocaleString("default", {month:"long",year:"numeric"}),value: d.flow_date } })
Toronto publishes monthly data on its taxed shelter system, and the City’s own dashboards do a good job presenting the headline numbers. This section goes a step further: interactive filters on every chart, a clear note on what the data captures and what it leaves out, and findings that usually stay buried in technical documentation.
The data covers every month from January 2018 to the most recent published month, sourced directly from the City of Toronto’s Shelter System Flow dataset. New data loads automatically each month, usually around the 15th, after the City publishes the prior month’s figures.
viewof reportingDate = Inputs.select(reportingMonthOptions, {label:"Reporting Month",format: d => d.label,value: reportingMonthOptions[0]})
Note: Oracle APEX renders this as a donut chart. Observable Plot does not support pie/donut natively — shown here as a percentage bar with the same data and colors.
This dashboard is also available as an Oracle APEX dashboard built on the same database — useful if you want to see how the same data renders in Oracle’s native low-code platform, or if you’re curious about the SQL and APEX configuration behind it.
How to read this data
These numbers reflect the shelter system, not all homelessness. The data captures people who used a City-funded overnight service at least once in the past three months. It doesn’t include people sleeping outside, in shelters that aren’t City-funded, or staying temporarily with friends or family. The City estimates that roughly 18% of people experiencing absolute homelessness in Toronto aren’t reflected in these numbers. Trends here reflect changes in the shelter system specifically — not in total homelessness in the city.
Key terms
The dashboards use a few terms that have specific meanings worth knowing before you read the charts.
Actively homeless. A person who has used City-funded shelter services at least once in the past three months and hasn’t been recorded as moving to permanent housing. This is the headline number on every dashboard. Because it looks back three months, the count includes people who may not be in shelter on any given night.
Chronic homelessness. The federal definition: someone who has spent at least 180 nights in shelter over the past year, or at least 546 nights over the past three years. A person can meet this definition while still actively in the shelter system — they don’t need to have left and returned.
Inflow categories (people entering the shelter system this month):
Newly Identified. People entering the shelter system for the first time. One exception for the “Chronic” group: in that row, this column counts people who became chronically homeless during the reporting month, regardless of how long they’d already been using the shelter system.
Returned from Permanent Housing. People who previously moved to permanent housing and have come back to the shelter system.
Returned to Shelter. People who were in the system, didn’t use it for at least three months, and have now returned.
Outflow categories (people leaving the shelter system this month):
Moved to Permanent Housing. People who left the shelter system for permanent housing.
Became Inactive. People who haven’t used shelter services in the past three months, including the reporting month.
html`<div class="crisis-hero"> <div class="hero-number">${_ratio}×</div> <div class="hero-text"> <div class="hero-label">more <strong>men</strong> than women are actively homeless in Toronto's shelter system</div> <div class="hero-sub"><strong>${_menCount.toLocaleString()} men</strong> · ${_womenCount.toLocaleString()} women · ${_transCount.toLocaleString()} transgender, non-binary, or two-spirit · ${_flowMonth}</div> </div></div>`
_genderStats = [ {label:"Men",mean: _menMean,ci_lo: _menMean -1.96* _menSE,ci_hi: _menMean +1.96* _menSE,isFocus:true}, {label:"Women",mean: _womenMean,ci_lo: _womenMean -1.96* _womenSE,ci_hi: _womenMean +1.96* _womenSE,isFocus:false}, {label:"Trans/NB/2S",mean: _transMean,ci_lo: _transMean -1.96* _transSE,ci_hi: _transMean +1.96* _transSE,isFocus:false}].sort((a, b) => b.mean- a.mean)Plot.plot({ width,height:300,marginBottom:36,marginLeft:70,marginRight:20,title:`Mean monthly actively homeless count by gender — ${_yearRange} (${_n1} months)`,caption:"Error bars show 95% confidence intervals of the monthly mean. Source: City of Toronto Shelter System Flow dataset (open.toronto.ca).",x: {label:null,domain: _genderStats.map(d => d.label)},y: {label:"Mean individuals actively homeless / month",grid:true,tickFormat: d => d.toLocaleString(),domain: [0,Math.max(..._genderStats.map(d => d.ci_hi)) *1.12]},marks: [ Plot.barY(_genderStats, {x:"label",y:"mean",fill: d => d.isFocus?"#ff3b30":"#4a6fa5",fillOpacity: d => d.isFocus?1:0.55 }), Plot.ruleY(_genderStats, {x:"label",y1:"ci_lo",y2:"ci_hi",stroke: _chartStroke,strokeWidth:1.8 }), Plot.tickY(_genderStats, {x:"label",y:"ci_hi",stroke: _chartStroke,strokeWidth:1.8,insetLeft:18,insetRight:18 }), Plot.tickY(_genderStats, {x:"label",y:"ci_lo",stroke: _chartStroke,strokeWidth:1.8,insetLeft:18,insetRight:18 }), Plot.text(_genderStats, {x:"label",y:"ci_hi",text: d =>Math.round(d.mean).toLocaleString(),dy:-10,textAnchor:"middle",fontSize:13,fill: d => d.isFocus?"#cc1f15": _chartFg,fontWeight:"600" }), Plot.ruleY([0]) ]})
{const bg = bayesian_genderconst rr = bg.rate_ratiosconst pd = bg.pairwise_diffsconst diag = bg.diagnosticsconst fmt1 = x =>Number(x).toFixed(1)const fmtN = x =>Math.round(x).toLocaleString()const bayesPairs = [ {comp:"Men vs Women",rr: rr.men_vs_women,diff: pd.men_vs_women,note:false}, {comp:"Men vs Trans/NB/2S",rr: rr.men_vs_transnb2s,diff: pd.men_vs_transnb2s,note:true}, {comp:"Women vs Trans/NB/2S",rr: rr.women_vs_transnb2s,diff: pd.women_vs_transnb2s,note:true} ]const bayesRows = bayesPairs.map(p =>html`<tr> <td style="padding:3px 10px 3px 0">${p.comp}${p.note?html`<sup> †</sup>`:""}</td> <td style="padding:3px 10px;text-align:right;font-variant-numeric:tabular-nums">${fmt1(p.rr.median)}× (${fmt1(p.rr.hdi[0])}–${fmt1(p.rr.hdi[1])}) </td> <td style="padding:3px 0;text-align:right;font-variant-numeric:tabular-nums;font-weight:${p.note?"400":"700"}">${fmtN(p.diff.median)} (${fmtN(p.diff.hdi[0])}–${fmtN(p.diff.hdi[1])}) </td> </tr>`)returnhtml`<div class="bq-bayes"> <p style="margin:0 0 0.3rem"> <strong>Bayesian analysis (negative-binomial regression, fit with <code>brms</code> ${bg.brms_version} on ${bg.n_months} months of data):</strong> Weakly informative priors were used throughout. Sampling diagnostics: R̂ ${diag.max_rhat}, ESS ${diag.min_ess_bulk.toLocaleString()},${diag.n_divergent} divergent transitions. </p> <table> <thead><tr> <th style="text-align:left;padding:3px 10px 3px 0;font-weight:600">Comparison</th> <th style="text-align:right;padding:3px 10px;font-weight:600">Rate ratio (95% HDI)</th> <th style="text-align:right;padding:3px 0;font-weight:600">Difference in count (95% HDI)</th> </tr></thead> <tbody>${bayesRows}</tbody> </table> <p class="bq-note" style="margin:0 0 0.5rem"> † The Trans/NB/2S group averages ~${Math.round(_transMean)} individuals per month, a small count that is also likely under-reported in administrative shelter records. Tests involving this group should be interpreted with caution. </p> <p style="margin:0 0 0.3rem"> <strong>Men vs Women:</strong> Men's expected monthly count is roughly <strong>${fmt1(rr.men_vs_women.median)}× that of women</strong> (95% HDI ${fmt1(rr.men_vs_women.hdi[0])}–${fmt1(rr.men_vs_women.hdi[1])}×), an average gap of about <strong>${fmtN(pd.men_vs_women.median)} people per month</strong> (95% HDI ${fmtN(pd.men_vs_women.hdi[0])}–${fmtN(pd.men_vs_women.hdi[1])}). Across a wide range of reasonable priors, the posterior probability that the true mean monthly count of men exceeds women is indistinguishable from 1. </p> <div class="plain-eng"> <div class="pe-label">Plain English</div> A posterior probability indistinguishable from 1 means the estimated probability is close to 100% that men's true monthly count exceeds women's. The 95% HDI also indicates the gap is consistently large — men's running about ${fmt1(rr.men_vs_women.median)}× women's, or roughly ${fmtN(pd.men_vs_women.median)} more people per month. <strong>Men are the largest and most consistently over-represented group in the city's shelter system.</strong> </div> </div>`}
md`Men have accounted for at least **${_menShareMinPct}%** of Toronto's actively homeless population every single month on record (${_recordStartYear} to ${_recordEndYear}). That figure is the **lowest single-month share of men observed across the entire data record**; in most months the share is higher. The average monthly gap between men and everyone else (women plus transgender, non-binary, and two-spirit individuals combined) is roughly **${_gapAvgStr} people**, and it has barely moved as the total has risen and fallen. The pattern is not new, not subtle, and not hidden in the data. Yet it rarely makes headlines, and few of the city's public conversations about homelessness lead with it.`