Toronto 311, 2025

A small data story from City of Toronto Open Data

500,269 requests: what Toronto asked its city for in 2025

Every customer-initiated 311 request the City logged in 2025, loaded into SQLite and explored with SQL. Here are five things the data shows, and a few things it cannot.

request types
539
marked Completed
78.5%
busiest day
19 Feb
Start reading

1

February was the busiest month.

Requests ran between about 33,000 and 54,000 a month. January was the quietest at 32,793 and February the busiest at 53,707, and February is the shortest month of the year.

One day stands out.

19 Feb was the busiest day of the year with 6,168 requests. The five busiest days all fall between 14 Feb and 21 Feb.

The requests that day were about snow.

The top three request types on 19 Feb were Sidewalk Snow Clearing Required (2,000), Road Plowing Request (1,059) and Driveway Blocked By Plowed Snowbank (711). That pattern fits a snow event. I did not check weather records, so it is a reading of the data and not a finding.

Requests per month, 2025
  1. 32.8kJan 2025: 32,793 requests
  2. 53.7kFeb 2025: 53,707 requests
  3. 37.3kMar 2025: 37,317 requests
  4. 38.1kApr 2025: 38,111 requests
  5. 44.6kMay 2025: 44,571 requests
  6. 47.0kJun 2025: 46,971 requests
  7. 48.1kJul 2025: 48,120 requests
  8. 43.6kAug 2025: 43,555 requests
  9. 44.9kSep 2025: 44,851 requests
  10. 37.8kOct 2025: 37,840 requests
  11. 33.5kNov 2025: 33,525 requests
  12. 38.9kDec 2025: 38,908 requests

Five busiest days

  1. 19 Feb6,168
  2. 18 Feb5,076
  3. 20 Feb4,431
  4. 14 Feb3,948
  5. 21 Feb3,204

Top request types on 19 Feb

  1. Sidewalk Snow Clearing Required2,000
  2. Road Plowing Request1,059
  3. Driveway Blocked By Plowed Snowbank711
  4. School Zone Snow Clearing211
  5. Snow at Intersections - Impeded Mobility201
Show the numbers
Requests per month
MonthRequests
2025-0132,793
2025-0253,707
2025-0337,317
2025-0438,111
2025-0544,571
2025-0646,971
2025-0748,120
2025-0843,555
2025-0944,851
2025-1037,840
2025-1133,525
2025-1238,908
Five busiest days
DayRequests
2025-02-196,168
2025-02-185,076
2025-02-204,431
2025-02-143,948
2025-02-213,204
Top request types on the busiest day
Request type on 2025-02-19Requests
Sidewalk Snow Clearing Required2,000
Road Plowing Request1,059
Driveway Blocked By Plowed Snowbank711
School Zone Snow Clearing211
Snow at Intersections - Impeded Mobility201

2

The most common request is a damaged bin lid.

There are 539 different request types. The ten most common add up to 135,681 requests, which is 27.1% of everything. The other 529 types share the rest.

Ten most common request types
  1. Residential Bin Lid Damaged24,4484.89%
  2. The most common single request, and about one in twenty.
  3. Property Standards and Maintenance Violations17,0793.41%
  4. Pick up Dead Wildlife15,5133.1%
  5. Injured - Wildlife15,1923.04%
  6. The two wildlife requests together: 30,705.
  7. General Pruning12,8732.57%
  8. Road Pothole / Road Damage11,3042.26%
  9. Residential Bin Body or Handle Damaged11,2382.25%
  10. Sidewalk Snow Clearing Required10,2412.05%
  11. 2,000 of these came in on 19 Feb alone.
  12. Res / Garbage / Not Picked Up9,1591.83%
  13. Residential Furniture / Not Picked Up8,6341.73%
Show the numbers
Ten most common request types
Request typeRequests% of all
Residential Bin Lid Damaged24,4484.89
Property Standards and Maintenance Violations17,0793.41
Pick up Dead Wildlife15,5133.1
Injured - Wildlife15,1923.04
General Pruning12,8732.57
Road Pothole / Road Damage11,3042.26
Residential Bin Body or Handle Damaged11,2382.25
Sidewalk Snow Clearing Required10,2412.05
Res / Garbage / Not Picked Up9,1591.83
Residential Furniture / Not Picked Up8,6341.73

3

Three divisions handle 84% of requests.

Solid Waste Management Services got 170,443 requests (34.1%), Transportation Services got 130,056 (26.0%) and Municipal Licensing & Standards got 117,630 (23.5%). Overall, 78.5% of all requests are marked Completed.

Requests by division

  1. Solid Waste Management Services170,44334.1%
  2. Transportation Services130,05626.0%
  3. Municipal Licensing & Standards117,63023.5%
  4. Toronto Water36,8397.4%
  5. Environment, Climate & Forestry36,1747.2%
  6. 3117,6901.5%
  7. Parks and Recreation1,4370.3%

How each division's requests ended up

  • Completed
  • Closed
  • In Progress
  • New
  • Cancelled
  • Unknown
  • Solid Waste Management Services82%Completed 82.1%Closed 1.4%In Progress 1.5%New 0.2%15%Cancelled 14.7%Unknown 0.1%
  • Transportation Services86%Completed 86.3%Closed 1.7%In Progress 2.8%New 3.8%Cancelled 2.5%Unknown 2.9%
  • Municipal Licensing & Standards73%Completed 73.3%Closed 4.5%In Progress 5.6%New 0.0%14%Cancelled 14.1%Unknown 2.5%
  • Toronto Water83%Completed 83.2%Closed 0.7%In Progress 4.9%New 0.8%10%Cancelled 10.3%Unknown 0.1%
  • Environment, Climate & Forestry42%Completed 41.5%20%In Progress 19.9%31%New 31.2%Cancelled 7.4%Unknown 0.1%
  • 31198%Completed 97.6%In Progress 0.9%New 0.4%Cancelled 1.1%
  • Parks and Recreation79%Completed 78.9%In Progress 8.3%13%Cancelled 12.7%Unknown 0.1%

Environment, Climate & Forestry stands out: 31.2% of its requests are still marked New, against 3.4% overall. I did not look into why. It could be reporting practice, workload or timing.

Show the numbers
Requests by division
DivisionRequests% of all
Solid Waste Management Services170,44334.1
Transportation Services130,05626.0
Municipal Licensing & Standards117,63023.5
Toronto Water36,8397.4
Environment, Climate & Forestry36,1747.2
3117,6901.5
Parks and Recreation1,4370.3
Requests by division and status
DivisionCompletedClosedIn ProgressNewCancelledUnknown
Solid Waste Management Services139,9292,3352,53931125,093236
Transportation Services112,2332,2483,6894,8963,2603,730
Municipal Licensing & Standards86,1815,3346,613316,5972,902
Toronto Water30,6602501,8212893,79623
Environment, Climate & Forestry15,01307,19511,2832,65924
3117,50307134820
Parks and Recreation1,134011901822
Overall status
StatusRequests% of all
Completed392,65378.5
Cancelled51,66910.3
In Progress22,0474.4
New16,8163.4
Closed10,1672.0
Unknown6,9171.4

4

Requests follow the working week.

Wednesday is the busiest weekday (90,155) and Sunday the quietest (38,291). Weekends are 16.7% of requests; spread evenly over seven days they would be 28.6%. Only 2.7% arrive between midnight and 6 am.

Requests by weekday and hour of day
Mon
Tue
Wed
Thu
Fri
Sat
Sun

The busiest hour of the week: Wednesday 10:00, 7,847 requests. Hover, tap or use the arrow keys to read another.

Show the numbers
Requests by weekday
WeekdayRequests
Monday71,725
Tuesday86,366
Wednesday90,155
Thursday85,772
Friday82,897
Saturday45,063
Sunday38,291
Requests by hour of day
HourRequests
00:004,509
01:002,725
02:001,767
03:001,326
04:001,207
05:002,096
06:005,242
07:0012,717
08:0028,599
09:0041,194
10:0043,231
11:0043,275
12:0039,701
13:0039,165
14:0037,958
15:0036,636
16:0034,010
17:0032,067
18:0025,643
19:0020,355
20:0016,237
21:0013,140
22:009,579
23:007,890

5

Toronto-Danforth had the most requests, but that is a count, not a rate.

The data has no population or household figures, so this says where requests were made, not which ward has bigger problems.

Ten wards with the most requests
  1. Toronto-Danforth (14)30,476
  2. Davenport (09)27,124
  3. University-Rosedale (11)27,122
  4. Beaches-East York (19)27,018
  5. Etobicoke-Lakeshore (03)25,874
  6. Parkdale-High Park (04)25,517
  7. Eglinton-Lawrence (08)24,221
  8. Scarborough Southwest (20)22,300
  9. Toronto-St. Paul's (12)21,747
  10. Spadina-Fort York (10)21,243
Show the numbers
Ten wards with the most requests
WardRequests% cancelled
Toronto-Danforth (14)30,47612.9
Davenport (09)27,1249.6
University-Rosedale (11)27,12210.5
Beaches-East York (19)27,01812.1
Etobicoke-Lakeshore (03)25,8749.3
Parkdale-High Park (04)25,51711.0
Eglinton-Lawrence (08)24,22110.6
Scarborough Southwest (20)22,30010.6
Toronto-St. Paul's (12)21,7478.9
Spadina-Fort York (10)21,2439.0

Before you quote any of this

How it was made

  1. Download. analysis.py asks the City's open data API for the 2025 file.
  2. Repair and load. 36,189 rows had a comma inside an unquoted value, and the file is not valid UTF-8. The script fixes both, then loads 500,269 rows into SQLite. No rows were dropped.
  3. Ask questions in SQL. Ten queries, all below. Their results are saved as CSV files in results/.
  4. Build this page. build_web.py reads those CSV files, so every figure here can be traced to a query.
Read the ten SQL queries
01_monthly_volume.sqlNumber of 311 service requests created in each calendar month.
-- Number of 311 service requests created in each calendar month.
SELECT strftime('%Y-%m', created_at) AS month,
       COUNT(*)                      AS requests
FROM   service_requests
GROUP  BY month
ORDER  BY month;
02_top_request_types.sqlThe 10 most common service request types, with their share of all requests.
-- The 10 most common service request types, with their share of all requests.
SELECT request_type,
       COUNT(*) AS requests,
       ROUND(100.0 * COUNT(*) / (SELECT COUNT(*) FROM service_requests), 2) AS pct_of_total
FROM   service_requests
GROUP  BY request_type
ORDER  BY requests DESC
LIMIT  10;
03_division_status_mix.sqlStatus breakdown per responsible division (counts). The Python script turns
-- Status breakdown per responsible division (counts). The Python script turns
-- this into percentages for the stacked bar chart.
SELECT division,
       status,
       COUNT(*) AS requests
FROM   service_requests
GROUP  BY division, status
ORDER  BY division, requests DESC;
04_hour_by_weekday.sqlRequests by day of week (0 = Sunday ... 6 = Saturday) and hour of day.
-- Requests by day of week (0 = Sunday ... 6 = Saturday) and hour of day.
-- Feeds the heatmap.
SELECT CAST(strftime('%w', created_at) AS INTEGER) AS weekday,
       CAST(strftime('%H', created_at) AS INTEGER) AS hour,
       COUNT(*)                                     AS requests
FROM   service_requests
GROUP  BY weekday, hour
ORDER  BY weekday, hour;
05_top_wards.sqlWards with the most requests, and each ward's share of requests that ended
-- Wards with the most requests, and each ward's share of requests that ended
-- up Cancelled. Wards with a missing name are excluded.
SELECT ward,
       COUNT(*) AS requests,
       ROUND(100.0 * SUM(status = 'Cancelled') / COUNT(*), 1) AS pct_cancelled
FROM   service_requests
WHERE  ward IS NOT NULL
GROUP  BY ward
ORDER  BY requests DESC
LIMIT  10;
06_data_quality.sqlBasic data-quality profile: row count, date range and missing values.
-- Basic data-quality profile: row count, date range and missing values.
SELECT COUNT(*)                                        AS total_rows,
       MIN(created_at)                                 AS first_request,
       MAX(created_at)                                 AS last_request,
       SUM(ward IS NULL OR ward = 'Unknown')       AS missing_ward,
       SUM(request_type IS NULL)                       AS missing_type,
       SUM(location_kind = 'Intersection')             AS rows_located_by_intersection,
       COUNT(DISTINCT request_type)                    AS distinct_request_types
FROM   service_requests;
07_busiest_days.sqlThe five busiest individual days.
-- The five busiest individual days.
SELECT date(created_at) AS day,
       COUNT(*)         AS requests
FROM   service_requests
GROUP  BY day
ORDER  BY requests DESC
LIMIT  5;
08_status_overall.sqlOverall status distribution.
-- Overall status distribution.
SELECT status,
       COUNT(*) AS requests,
       ROUND(100.0 * COUNT(*) / (SELECT COUNT(*) FROM service_requests), 1) AS pct
FROM   service_requests
GROUP  BY status
ORDER  BY requests DESC;
09_division_volume.sqlRequests per division.
-- Requests per division.
SELECT division,
       COUNT(*) AS requests,
       ROUND(100.0 * COUNT(*) / (SELECT COUNT(*) FROM service_requests), 1) AS pct
FROM   service_requests
GROUP  BY division
ORDER  BY requests DESC;
10_busiest_day_types.sqlWhat people were asking for on the single busiest day of the year.
-- What people were asking for on the single busiest day of the year.
SELECT request_type,
       COUNT(*) AS requests
FROM   service_requests
WHERE  date(created_at) = (SELECT date(created_at)
                           FROM   service_requests
                           GROUP  BY date(created_at)
                           ORDER  BY COUNT(*) DESC
                           LIMIT  1)
GROUP  BY request_type
ORDER  BY requests DESC
LIMIT  5;

Data source: 311 Service Requests - Customer Initiated, City of Toronto Open Data (resource "311 Service Requests 2025"), downloaded through the portal's CKAN API on 2026-10-17. The City refreshes the dataset monthly, so running it later may give slightly different numbers. Check the dataset page for licence terms before redistributing the raw data.

How to read this

  1. Just scroll. Each section is one finding. On a wide screen the chart stays in place in the first section and changes as you read the text beside it.
  2. Read the numbers off the chart. Bars and months are labelled. On the heatmap in section 4, hover, tap, or use the arrow keys to read any single hour.
  3. Switch the view in section 3. Choose Volume to see how many requests each City division gets, or Outcomes to see how those requests ended up.
  4. Open the table behind a chart. The line that says Show the numbers under each chart opens the exact figures, straight from the results files.
  5. Check the method. The last section lists the cleaning steps and has all ten SQL queries. Click a query to read it.
Words you will see
Request
One 311 service request: someone reporting a problem or asking the City for a service.
Division
The City department responsible for the request, such as Transportation Services.
Ward
One of Toronto's electoral areas. The data names the ward a request was made in.
Status
Where a request stood on the day the file was published: Completed, Cancelled, In Progress, New, Closed or Unknown.
Snapshot
A single point in time. Late-year requests have had less time to be finished, so status is not a fair race.

Why I built this

I wanted one project that used real data from start to finish instead of a tidy sample file. I picked Toronto's 311 requests because it is the city I'm in, and because the file is big and awkward enough to need real work.

Before I could load a single row I had to fix an encoding problem and about 36,000 rows where a value contains an unquoted comma. After that I used SQL to ask the questions and Python only to load, chart and write up the results.

The file has no closing dates, so I did not work out how long requests take. I would rather say what the data cannot show than guess.