Skip to content

Instantly share code, notes, and snippets.

@vsapsai
Last active May 22, 2017 03:27
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save vsapsai/225a0f3ee7698071e5306d7602368fb3 to your computer and use it in GitHub Desktop.
Percentile-ish income (not wealth) distribution in Canada for tax year 2014
license: mit

Show percentage of Canada population having different ranges of income according to filed tax returns in 2014. Percentages are ordered by income amount so you can get rough percentiles for different income ranges.

Note that the plot shows income distribution, not wealth distribution. Usually inequality manifests itself in accumulated wealth and in modern inegalitarian societies income distribution isn't very skewed. I am not an expert in the subject so if you are interested in income or wealth distribution, please find actual credible sources.

Data is taken from Canada Revenue Agency.

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

See also

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Income (not wealth) distribution in Canada</title>
<style>
svg {
cursor: crosshair;
}
.axis.x-axis .domain {
display: none;
}
#interactive line {
fill: none;
stroke: gray;
stroke-width: 1px;
stroke-dasharray: 4, 2;
}
#interactive text {
font-size: 12px;
font-family: sans-serif;
}
#interactive.inactive line, #interactive.inactive text {
display: none;
}
</style>
</head>
<body>
<svg width="960" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
margin = {top: 20, right: 40, 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",
],
// Use upper bound of the bucket because I expect that's the value people
// will be looking for when checking their own income.
incomeBucketDisplayedIncome = {
"under-5_000": 5000,
"5_000-10_000": 10000,
"10_000-15_000": 15000,
"15_000-20_000": 20000,
"20_000-25_000": 25000,
"25_000-30_000": 30000,
"30_000-35_000": 35000,
"35_000-40_000": 40000,
"40_000-45_000": 45000,
"45_000-50_000": 50000,
"50_000-55_000": 55000,
"55_000-60_000": 60000,
"60_000-70_000": 70000,
"70_000-80_000": 80000,
"80_000-90_000": 90000,
"90_000-100_000": 100000,
"100_000-150_000": 150000,
"150_000-250_000": 250000,
"over-250_000": 500000, // fairly arbitrary number just to make the plot
// look nice and not as depressing as the real life.
};
var xScale = d3.scaleLinear()
.range([0, width])
.domain([0, 1.0]);
var yScale = d3.scaleLinear()
.range([height, 0])
.domain([0, d3.max(Object.values(incomeBucketDisplayedIncome))]);
d3.csv("/vsapsai/raw/155794a4b1d3b432be4e67accd247c23/cndtbl-summary.csv", function(error, data) {
if (error) {
throw error;
}
//console.log(data);
var specificRow = null;
var i, length = data.length;
for (i = 0; i < length; i++) {
var row = data[i];
if ((row.classification.trim() === "CANADA TOTAL") && (row.year === "2014")) {
specificRow = row;
break;
}
}
//console.log(specificRow);
// Prepare data for plotting.
var totalPopulation = parseInt(specificRow.total);
var bucketData = [];
incomeBuckets.forEach(function(bucketName) {
var bucketPopulation = parseInt(specificRow[bucketName]);
bucketData.push({
name: bucketName,
population: bucketPopulation,
percentage: (bucketPopulation / totalPopulation),
});
});
// Add percentage offset for all buckets.
var percentageOffset = 0;
bucketData.forEach(function(bucketDatum) {
bucketDatum.percentageOffset = percentageOffset;
percentageOffset += bucketDatum.percentage;
});
//console.log(bucketData);
// Plot data.
g.append("g")
.selectAll("rect")
.data(bucketData)
.enter().append("rect")
.attr("x", function(d) { return xScale(d.percentageOffset); })
.attr("y", function(d) { return yScale(incomeBucketDisplayedIncome[d.name]); })
.attr("width", function(d) { return xScale(d.percentage); })
.attr("height", function(d) { return height - yScale(incomeBucketDisplayedIncome[d.name]); })
.attr("fill", d3.schemeCategory10[0]);
g.append("g")
.attr("class", "axis y-axis")
.call(d3.axisLeft(yScale).ticks(null, "$s"));
g.append("g")
.attr("class", "axis x-axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale).tickFormat(d3.format(".0%")));
// Interactive part with mouse cursor tracking.
var interactiveGroup = g.append("g")
.attr("id", "interactive")
.classed("inactive", true);
var horizontalLine = interactiveGroup.append("line")
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", width)
.attr("y2", 0);
var verticalLine = interactiveGroup.append("line")
.attr("x1", 0)
.attr("y1", height)
.attr("x2", 0)
.attr("y2", 0);
var yValueMarker = interactiveGroup.append("text")
.attr("x", 2)
.attr("y", 0)
.attr("dy", "-0.3em")
.attr("text-anchor", "start");
var yValueFormat = d3.format("$.5s");
var xValueMarker = interactiveGroup.append("text")
.attr("x", 0)
.attr("y", height)
.attr("dx", "0.3em")
.attr("dy", "-0.3em")
.attr("text-anchor", "start");
var xValueFormat = d3.format(".1%");
svg.on("mouseout", function() {
interactiveGroup.classed("inactive", true);
});
svg.on("mousemove", updateMouseTracking);
function updateMouseTracking() {
var coordinates = d3.mouse(this);
var x = coordinates[0] - margin.left,
y = coordinates[1] - margin.top;
horizontalLine
.attr("y1", y)
.attr("y2", y)
.attr("x2", x);
verticalLine
.attr("x1", x)
.attr("x2", x)
.attr("y2", y);
var isInBounds = ((0 <= x) && (x <= width) &&
(0 <= y) && (y <= height));
if (isInBounds) {
yValueMarker
.attr("y", y)
.text(yValueFormat(yScale.invert(y)));
xValueMarker
.attr("x", x)
.text(xValueFormat(xScale.invert(x)));
}
interactiveGroup.classed("inactive", !isInBounds);
}
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment