Inspired by reading this blog: https://garstats.wordpress.com/2017/11/28/trimmed-means/:
Uses prob.js with randon.js to generate the distribution.
Built with blockbuilder.org
xxxxxxxxxx
<head>
<meta charset="utf-8">
<link href="https://fonts.googleapis.com/css?family=Adamina" rel="stylesheet">
<script src="random.min.js"></script>
<script src="prob.min.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
body {
font-family: 'Adamina', serif;
margin: 50;
}
</style>
</head>
<body>
<h1>Trimmed Mean</h1>
<p>Inspired by reading this blog: <a href="https://garstats.wordpress.com/2017/11/28/trimmed-means/">https://garstats.wordpress.com/2017/11/28/trimmed-means/</a>:</p>
<p><i>Trimmed means are robust estimators of central tendency. To compute a trimmed mean, we remove a predetermined amount of observations on each side of a distribution, and average the remaining observations.</i></p>
<p>Choose the trim amount (from 0 to 0.45) to see the effect on the mean.</p>
<div id="slidecontainer">
<p>Value: <span id="trim-value">0</span></p>
</div>
<script>
const width = 1000
const height = 250
const margin = { "top": 20, "bottom": 20, "left": 20, "right": 20 }
const data = []
let trimAmount = 0
let f = Prob.lognormal(1, 0.6);
const dataLength = 1000
for (var i = 0; i < dataLength; i++) {
data.push(Math.round(f()))
};
data.sort(d3.ascending)
const dataNested = d3.nest()
.key(function (d) { return d })
.rollup(function (leaves) { return leaves.length; })
.entries(data)
dataNested.forEach(function (d) {
d.keyNumber = +d.key;
})
let xScale = d3.scaleLinear()
.range([0, width])
.domain([0, d3.max(dataNested, function (d) { return d.keyNumber })])
let yScale = d3.scaleLinear()
.range([height, 0])
.domain([0, d3.max(dataNested, function (d) { return d.value })])
let area = d3.area()
.x(function (d) { return xScale(d.keyNumber) })
.y0(function (d) { return yScale(0) })
.y1(function (d) { return yScale(d.value) })
.curve(d3.curveCatmullRom.alpha(0.5));
let line = d3.line()
.x(function (d) { return xScale(d.keyNumber) })
.y(function (d) { return yScale(d.value) })
.curve(d3.curveCatmullRom.alpha(0.5));
let slider = d3.select("#slidecontainer").append("input")
.attr("type", "range")
.attr("min", 0)
.attr("max", 0.4)
.attr("value", 0)
.attr("step", 0.05)
.attr("id", "trim-slider")
.on("input", function input() {
update();
});
let svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
let g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale))
g.append("g")
.append("path")
.datum(dataNested)
.style("fill", "SandyBrown")
.attr("d", area);
g.append("g")
.append("path")
.datum(dataNested)
.style("stroke", "#a7540c")
.style("stroke-width", 3)
.style("fill", "none")
.attr("d", line)
let trimRect = g.selectAll("rect")
.data(trimExtent(data, trimAmount))
.enter()
.append("rect")
.attr("x", function (d) {
return xScale(d.start)
})
.attr("y", 0)
.attr("width", function (d) {
return xScale(d.end) - xScale(d.start)
})
.attr("height", height)
.style("fill", "grey")
.style("opacity", 0.3)
let avgLine = g.selectAll(".avg-line")
.data([trimmedMean(data, trimAmount)])
.enter()
.append("g")
.attr("class", "avg-line")
.attr("transform", function (d) {
return "translate(" + xScale(d) + ",0)"
})
avgLine.append("text")
.text(function (d) {
return "mean: " + round(d)
})
.attr("x", 5)
.attr("y", height * 0.9)
avgLine.append("line")
.attr("y2", height)
.style("stroke", "black")
.style("stroke-width", 3)
.style("fill", "none")
function trimmedMean(values, trim) {
let l = values.length
let n = Math.floor(l * trim)
let trimmedValues = values.slice(n, l - n)
let sum = trimmedValues.reduce((previous, current) => current += previous);
return sum / (trimmedValues.length)
}
function trimExtent(values, trim) {
let trimmedValues = values.sort(d3.ascending)
let l = values.length
let n = Math.floor(l * trim)
return [{ "start": trimmedValues[n], "end": trimmedValues[l - n - 1] }]
}
function update() {
var t = document.getElementById("trim-slider").value;
d3.select("#trim-value").text(t)
trimRect.data(trimExtent(data, t))
.transition()
.attr("x", function (d) {
return xScale(d.start)
})
.attr("width", function (d) {
return xScale(d.end) - xScale(d.start)
})
let tm = trimmedMean(data, t)
avgLine.data([tm])
.transition()
.attr("transform", function (d) {
return "translate(" + xScale(d) + ",0)"
})
avgLine.select("text")
.text("mean: " + round(tm))
}
function round(n) {
return Math.round(n * 100)/100
}
</script>
</body>
https://d3js.org/d3.v4.min.js