Skip to content

Instantly share code, notes, and snippets.

@vsapsai
Last active May 7, 2017 21:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vsapsai/155794a4b1d3b432be4e67accd247c23 to your computer and use it in GitHub Desktop.
Save vsapsai/155794a4b1d3b432be4e67accd247c23 to your computer and use it in GitHub Desktop.
Income distribution in Canada
license: gpl-3.0

Show income distribution in Canada according to filed tax returns.

Data is taken from Canada Revenue Agency. 2012 filing year is excluded because it has less columns (starts from under $10,000, not from under $5,000).

Important: data is nominal and not adjusted for inflation. Be careful comparing years, including comparison with your own income in current year.

Visualization is based on

Interesting from visualization perspective are spread out labels for x axis.

province CD CSD CMA-CA classification description total total_income under-5_000 5_000-10_000 10_000-15_000 15_000-20_000 20_000-25_000 25_000-30_000 30_000-35_000 35_000-40_000 40_000-45_000 45_000-50_000 50_000-55_000 55_000-60_000 60_000-70_000 70_000-80_000 80_000-90_000 90_000-100_000 100_000-150_000 150_000-250_000 over-250_000 year
10 00 000 000 TOTAL-PROV Total Prov/Terr 432230 18985101000 32900 28350 41020 50840 34500 28830 26290 22780 22300 17830 15250 12850 20520 16130 13900 11190 24510 9390 2840 2014
11 00 000 000 TOTAL-PROV Total Prov/Terr 114540 4426783000 8230 7430 9180 12300 10260 9190 8480 7710 6920 5940 4900 4020 5900 4390 2920 1790 3480 1130 400 2014
12 00 000 000 TOTAL-PROV Total Prov/Terr 744660 30505235000 65000 58090 56470 74500 63770 49560 48200 44980 42060 34020 28860 25020 39530 31100 24130 15800 29690 10160 3720 2014
13 00 000 000 TOTAL-PROV Total Prov/Terr 608380 23502551000 48070 47190 49770 67300 52810 46230 41980 39180 35370 27830 22750 18930 30520 23880 15790 10100 21770 6630 2290 2014
24 00 000 000 TOTAL-PROV Total Prov/Terr 6509280 266919553000 532510 469660 603660 692370 510590 424050 416990 403880 384430 312860 264920 225170 346190 270090 173100 118160 236040 82140 42480 2014
35 00 000 000 TOTAL-PROV Total Prov/Terr 10401150 487793493000 1124170 688520 929720 914270 741760 572350 549270 535880 528400 460510 396360 350640 583540 463930 362480 307520 573570 212220 106050 2014
46 00 000 000 TOTAL-PROV Total Prov/Terr 963650 39984236000 118110 61220 72110 82180 69310 61880 62830 57680 55620 45500 38800 33360 53940 41060 31180 20290 40120 12560 5910 2014
47 00 000 000 TOTAL-PROV Total Prov/Terr 837700 40857138000 85000 43630 55390 72200 56650 47990 46290 44610 46290 40540 35030 30660 50960 41780 33580 24360 56290 19330 7110 2014
48 00 000 000 TOTAL-PROV Total Prov/Terr 3078920 192005770000 288710 150700 174040 212360 202790 169880 150340 145530 144780 131930 121630 109970 192010 159510 130240 114370 280270 136100 63760 2014
59 00 000 000 TOTAL-PROV Total Prov/Terr 3662790 165525918000 393110 223370 337060 326420 269520 216010 200810 198310 188200 164030 141230 123570 206270 166920 119110 90160 194740 72940 31000 2014
60 00 000 000 TOTAL-PROV Total Prov/Terr 27560 1459519000 1620 1240 1630 2050 1850 1560 1410 1370 1370 1190 1150 1190 2250 1930 1560 1130 2310 620 140 2014
61 00 000 000 TOTAL-PROV Total Prov/Terr 31350 1839767000 3770 2080 1950 1970 1620 1280 1120 1100 1060 990 920 830 1660 1650 1480 1480 4830 1280 280 2014
62 00 000 000 TOTAL-PROV Total Prov/Terr 20670 978523000 3520 2420 1770 1490 1090 920 720 670 520 460 370 360 660 710 770 810 2560 760 100 2014
00 00 000 000 CANADA TOTAL Canada Total 27432860 1274783820000 2704720 1783890 2333760 2510250 2016520 1629730 1554740 1503670 1457300 1243620 1072180 936570 1533960 1223080 910240 717160 1470160 565250 266060 2014
10 00 000 000 TOTAL-PROV Total Prov/Terr 428840 18099875000 32900 28980 42140 51890 34740 29680 26730 23320 22200 17480 14840 12550 19640 15900 13510 9450 22330 7980 2580 2013
11 00 000 000 TOTAL-PROV Total Prov/Terr 113260 4280967000 8300 7580 9360 12570 10240 9360 8670 7670 7150 5600 4630 3760 5630 4110 2340 1580 3220 1070 420 2013
12 00 000 000 TOTAL-PROV Total Prov/Terr 740700 29497634000 65960 60420 56940 76900 61320 52020 50160 45400 41270 32520 28300 24130 38440 31000 21600 14050 27160 9610 3510 2013
13 00 000 000 TOTAL-PROV Total Prov/Terr 604680 22748967000 49360 48670 50800 68430 53510 46090 42380 39110 35030 26870 21830 18180 29370 22820 14690 9650 19280 6360 2250 2013
24 00 000 000 TOTAL-PROV Total Prov/Terr 6454030 258124100000 536280 481800 612270 704060 510290 425620 418800 402650 375700 304890 259050 217960 334430 255960 161760 111340 222320 79260 39590 2013
35 00 000 000 TOTAL-PROV Total Prov/Terr 10250730 468128061000 1143070 731380 915680 920700 724990 565790 546290 533880 515560 450100 390110 340330 565660 445480 347300 285100 528810 198560 101930 2013
46 00 000 000 TOTAL-PROV Total Prov/Terr 951670 38516170000 116390 63830 72760 83740 70480 62120 63160 57770 55090 44190 37830 32000 52480 39080 29420 18500 35700 11490 5660 2013
47 00 000 000 TOTAL-PROV Total Prov/Terr 827570 39097622000 84850 45090 57780 73840 56780 49070 47680 45330 46000 38660 33300 29330 49470 39360 31920 23100 51750 17670 6590 2013
48 00 000 000 TOTAL-PROV Total Prov/Terr 2994710 180333352000 277180 157790 178040 215430 201970 165850 149270 143800 143180 128670 118090 106060 185750 153770 124400 107900 257650 124210 55720 2013
59 00 000 000 TOTAL-PROV Total Prov/Terr 3589150 158805033000 399540 229980 340520 326780 266020 213330 200180 193110 183650 157310 135550 118730 198080 154000 118930 84520 175540 64090 29290 2013
60 00 000 000 TOTAL-PROV Total Prov/Terr 27220 1423286000 1620 1300 1650 2110 1820 1550 1470 1400 1320 1200 1170 1110 2220 1840 1550 1050 2110 580 150 2013
61 00 000 000 TOTAL-PROV Total Prov/Terr 30950 1773335000 3750 2080 2020 1980 1660 1260 1140 1070 1080 970 870 860 1660 1630 1490 1450 4490 1190 280 2013
62 00 000 000 TOTAL-PROV Total Prov/Terr 20160 936996000 3320 2370 1750 1510 1150 900 720 620 540 460 410 370 660 690 800 720 2410 680 90 2013
00 00 000 000 CANADA TOTAL Canada Total 27033670 1221766079000 2722500 1861250 2341710 2539940 1994970 1622620 1556640 1495110 1427770 1208910 1045990 905380 1483510 1165640 869730 668420 1352780 522740 248060 2013
#!/usr/bin/env python
import csv
FIELD_NAMES = (
"province", "CD", "CSD", "CMA-CA", "classification", "description",
"total", "total_income",
"under-5_000", "5_000-10_000", "10_000-15_000", "15_000-20_000",
"20_000-25_000", "25_000-30_000", "30_000-35_000", "35_000-40_000",
"40_000-45_000", "45_000-50_000", "50_000-55_000", "55_000-60_000",
"60_000-70_000", "70_000-80_000", "80_000-90_000", "90_000-100_000",
"100_000-150_000", "150_000-250_000", "over-250_000")
TOTAL_CLASSIFICATIONS = frozenset(["TOTAL-PROV", "CANADA TOTAL"])
def main():
# Read data and leave only totals for provinces and for the entire country.
files = {
#"2012": "cndtbl1-2012.csv",
"2013": "cndtbl1-2013.csv",
"2014": "cndtbl1-2014.csv",
}
totals = []
for year, filename in files.items():
with open(filename, "r") as f:
reader = csv.DictReader(f, fieldnames=FIELD_NAMES)
summary_rows = [row for row in reader if row["classification"].strip() in TOTAL_CLASSIFICATIONS]
for row in summary_rows:
row["year"] = year
totals.extend(summary_rows)
# Write filtered data.
with open("cndtbl-summary.csv", "w") as output_file:
new_fieldnames = list(FIELD_NAMES)
new_fieldnames.append("year")
writer = csv.DictWriter(output_file, fieldnames=new_fieldnames)
writer.writeheader()
for row in totals:
writer.writerow(row)
if __name__ == "__main__":
main()
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Income distribution in Canada</title>
<style>
.axis .domain {
display: none;
}
.grid .line {
fill: none;
stroke: white;
stroke-width: 1px;
stroke-dasharray: 1, 5;
}
.grid .line.major {
stroke-dasharray: 0;
}
</style>
</head>
<body>
<svg width="1280" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
margin = {top: 20, right: 20, bottom: 50, left: 40},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var incomeBuckets = [
"under-5_000", "5_000-10_000", "10_000-15_000", "15_000-20_000",
"20_000-25_000", "25_000-30_000", "30_000-35_000", "35_000-40_000",
"40_000-45_000", "45_000-50_000", "50_000-55_000", "55_000-60_000",
"60_000-70_000", "70_000-80_000", "80_000-90_000", "90_000-100_000",
"100_000-150_000", "150_000-250_000", "over-250_000",
],
incomeBucketLabels = [
"Under $5,000", "$5,000 to $9,999",
"$10,000 to $14,999", "$15,000 to $19,999",
"$20,000 to $24,999", "$25,000 to $29,999",
"$30,000 to $34,999", "$35,000 to $39,999",
"$40,000 to $44,999", "$45,000 to $49,999",
"$50,000 to $54,999", "$55,000 to $59,999",
"$60,000 to $69,999",
"$70,000 to $79,999",
"$80,000 to $89,999",
"$90,000 to $99,999",
"$100,000 to $149,999",
"$150,000 to $249,999",
"$250,000 and over",
];
var firstLevelX = d3.scaleBand()
.rangeRound([0, width])
.paddingInner(0.1);
var secondLevelX = d3.scaleBand()
.padding(0.05);
var y = d3.scaleLinear()
.rangeRound([height, 0]);
var color = d3.scaleOrdinal(d3.schemeCategory20); // 20 just looks prettier than 10
d3.csv("cndtbl-summary.csv", function(error, data) {
if (error) {
throw error;
}
//console.log(data);
var totalData = data.filter(function(item) {
return item.classification.trim() === "CANADA TOTAL";
});
//console.log(totalData);
// Configure domains for scales.
firstLevelX.domain(incomeBuckets);
var years = totalData.map(function(item) { return item.year; }).sort();
secondLevelX
.domain(years)
.rangeRound([0, firstLevelX.bandwidth()]);
var yMax = d3.max(totalData, function(item) {
return d3.max(incomeBuckets, function(bucket) {
return parseInt(item[bucket]);
});
});
// Add 0.2 million to have some head space at the top.
y.domain([0, yMax + 200000]);
// Transform data: first level is income buckets, second level is years.
var processedData = {};
incomeBuckets.forEach(function(bucket) {
processedData[bucket] = {
bucket: bucket,
yearlyData: [],
};
});
totalData.forEach(function(yearData) {
var year = yearData.year;
incomeBuckets.forEach(function(bucket) {
processedData[bucket].yearlyData.push({
year: year,
value: parseInt(yearData[bucket]),
});
});
});
//console.log(processedData);
// Plot data.
g.append("g")
.selectAll("g")
.data(Object.values(processedData))
.enter().append("g")
.attr("transform", function(d) { return "translate(" + firstLevelX(d.bucket) + ",0)"; })
.selectAll("rect")
.data(function(d) { return d.yearlyData; })
.enter().append("rect")
.attr("x", function(d) { return secondLevelX(d.year); })
.attr("y", function(d) { return y(d.value); })
.attr("width", secondLevelX.bandwidth())
.attr("height", function(d) { return height - y(d.value); })
.attr("fill", function(d) { return color(d.year); });
// Draw text at different height because labels for consecutive bands overlap.
function customXAxis(g) {
// Based on Axis Styling https://bl.ocks.org/mbostock/3371592
var xAxis = d3.axisBottom(firstLevelX.copy().domain(incomeBucketLabels));
g.call(xAxis);
g.selectAll(".tick:nth-child(odd) text").attr("dy", 20);
}
g.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(customXAxis);
g.append("g")
.attr("class", "axis")
.call(d3.axisLeft(y).ticks(null, "s"))
.append("text")
.attr("x", 2)
.attr("y", y(y.ticks().pop()) + 0.5)
.attr("dy", "0.32em")
.attr("fill", "#000")
.attr("font-weight", "bold")
.attr("text-anchor", "start")
.text("Population with income");
var yGridValues = y.ticks().filter(function(d) { return (0 < d) && (d <= yMax); });
var gridData = yGridValues.map(function(yValue) {
return [[0, yValue], [width, yValue]];
});
g.append("g")
.attr("class", "grid")
.selectAll("path")
.data(gridData)
.enter().append("path")
.attr("class", "line")
.attr("d", d3.line().y(function(d) { return y(d[1]); }))
.classed("major", function(d) { return (d[0][1] % 500000) === 0; });
var legend = g.append("g")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.attr("text-anchor", "end")
.selectAll("g")
.data(years)
.enter().append("g")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("x", width - 19)
.attr("width", 19)
.attr("height", 19)
.attr("fill", color);
legend.append("text")
.attr("x", width - 24)
.attr("y", 9.5)
.attr("dy", "0.32em")
.text(function(d) { return d + " tax year"; });
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment