箱型图(Boxplot)
箱型图概念解释:https://blog.csdn.net/aijiudu/article/details/89387328
箱型图,是一种用于显示一组数据分散情况资料的统计图,即每一个box表示的是一组数据。
通常采用五个数值来概括这一组数据,即:中位数
Q
2
Q2
Q2,下四分位数
Q
1
Q1
Q1,上四分位数
Q
3
Q3
Q3,上限和下限。
其中,
Q
2
=
{
a
(
n
+
1
)
/
2
,
o
d
d
a
n
/
2
+
a
n
/
2
+
1
2
,
e
v
e
n
Q2=\left\{\begin{aligned}&a_{(n+1)/2},odd\\&\frac{a_{n/2}+a_{n/2+1}}{2},even \end{aligned}\right.
Q2=⎩⎨⎧a(n+1)/2,odd2an/2+an/2+1,even,
Q
1
=
a
1
∗
(
n
+
1
)
4
Q1=a_{\frac{1*(n+1)}{4}}
Q1=a41∗(n+1),
Q
3
=
a
3
∗
(
n
+
1
)
4
Q3=a_{\frac{3*(n+1)}{4}}
Q3=a43∗(n+1)。
I
Q
R
=
Q
3
−
Q
1
IQR=Q3-Q1
IQR=Q3−Q1,上限为不超过
Q
3
+
1.5
∗
I
Q
R
Q3+1.5*IQR
Q3+1.5∗IQR的最大值,下限为不低于
Q
1
−
1.5
∗
I
Q
R
Q1-1.5*IQR
Q1−1.5∗IQR的最小值,超出上下限的数值均可看作异常值。
数据均来自网站:
https://github.com/pearmini/d3-barchart-demo?tdsourcetag=s_pctim_aiomsg
https://github.com/datasets/population
效果图:
代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>BoxPlot</title>
</head>
<body>
<button onClick="display()">show</button>
<select id="ID">
<option value="0">Death(%)</option>
<option value="1">Cure(%)</option>
<option value="2">Rate(per 10,000)</option>
</select>
<script src="https://d3js.org/d3.v5.js"></script>
<script>
let w = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
let h = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
w *= 0.98;
h *= 0.9;
const inf = 1e9 + 10;
let inner = {top: 50, right: 50, bottom: 50, left: 50};
let W = w - inner.left - inner.right;
let H = h - inner.top - inner.bottom;
let Data = new Array();
Initialize();
function display() {
let x = document.getElementById("ID");
let T = parseInt(x.value);
let DateData = new Array();
let NumData = new Array();
let Num = new Array();
let cnt = 0;
//不同的选项,排序的优先级不同
Data.sort(function (a, b) {
let stra = new Date(a.date), strb = new Date(b.date);
if (stra.getTime() === strb.getTime()) {
if (T === 0) return a.DeathRate - b.DeathRate;
if (T === 1) return a.CuredRate - b.CuredRate;
if (T === 2) return a.ComfirmedRate - b.ComfirmedRate;
} else return stra.getTime() - strb.getTime();
});
for (let i = 0; i < Data.length; ++i) {
if (i === 0 || Data[i].date !== Data[i - 1].date) {
if (i !== 0) cnt++;
DateData[cnt] = new Date(Data[i].date);
NumData[cnt] = new Array();
}
if (T === 0) NumData[cnt].push(Data[i].DeathRate);
if (T === 1) NumData[cnt].push(Data[i].CuredRate);
if (T === 2) NumData[cnt].push(Data[i].ComfirmedRate);
}
++cnt;
// 计算每组数据的Q1 Q2 Q3等
for (let i = 0; i < cnt; ++i) {
NumData[i].sort(function (a, b) {
return a - b;
});
let n = NumData[i].length;
let Q1, Q2, Q3, U, D;
Q1 = NumData[i][Math.round(n * 0.25)];
Q3 = NumData[i][Math.round(n * 0.75)];
if (n % 2 === 0) Q2 = (NumData[i][Math.ceil(n * 0.5)] + NumData[i][Math.floor(n * 0.5)]) * 0.5;
else Q2 = NumData[i][Math.round(n * 0.5)];
// alert(Q2);
U = 0;
D = inf;
let L = Q1 - 1.5 * (Q3 - Q1);
let R = Q3 + 1.5 * (Q3 - Q1);
for (let j = 0; j < NumData[i].length; ++j) {
if (NumData[i][j] >= L && NumData[i][j] <= R) {
U = Math.max(U, NumData[i][j]);
D = Math.min(D, NumData[i][j]);
}
}
Num[i] = {Q1: Q1, Q2: Q2, Q3: Q3, U: U, D: D};
}
console.log(Num);
let xScale = d3.scaleTime()
.domain([DateData[0], DateData[cnt - 1].setDate(DateData[cnt - 1].getDate() + 1)])
.range([0, w - inner.left - inner.right]);
let xAxis = d3.axisBottom(xScale).ticks(DateData.length);
let y1 = 100;
if (T === 0) y1 = 50;
if (T === 1) y1 = 50;
if (T === 2) y1 = 3.5;
// 横轴时间轴
let yScale = d3.scaleLinear()
.domain([0, y1])
.range([h - inner.top - inner.bottom, 0])
.nice();
let yAxis = d3.axisLeft(yScale);
let svg;
d3.select("body")
.selectAll("svg", function (d) {
if (d.id === "123") return this;
})
.remove();
svg = d3.select("body")
.append("svg", id = "123")
.attr("width", w)
.attr("height", h)
.append("g");
svg.append("g")
.attr("transform", "translate(" + inner.left + "," + (h - inner.bottom) + ")")
.call(xAxis.tickFormat(d3.timeFormat("%m-%d")));
svg.append("g")
.attr("transform", "translate(" + inner.left + "," + (inner.top) + ")")
.call(yAxis);
svg.selectAll("rect")
.data(Num)
.enter()
.append("rect")
.attr("x", function (d, i) {
return (i + 0.2) * (W / cnt) + inner.left;
})
.attr("y", function (d, i) {
let tot = d.Q3 / y1 * H;
return inner.top + (H - tot);
})
.attr("width", function (d, i) {
return W / cnt * 0.6;
})
.attr("height", function (d, i) {
return (d.Q3 - d.Q1) / y1 * H
})
.attr("fill", 'yellow')
.attr("stroke", 'black');
// 画线函数
function draw(x1, y1, x2, y2, l = 1, c = "black") {
svg.append("line")
.attr("x1", x1)
.attr("y1", y1)
.attr("x2", x2)
.attr("y2", y2)
.attr("stroke-width", l)
.attr("stroke", c);
}
for (let i = 0; i < cnt; ++i) {
let x1 = (i + 0.2) * (W / cnt) + inner.left;
let x2 = x1 + (W / cnt) * 0.6;
let y;
let tot = Num[i].Q2 / y1 * H;
y = inner.top + (H - tot);
draw(x1, y, x2, y, 2);
tot = Num[i].U / y1 * H;
y = inner.top + (H - tot);
// alert(y);
draw(x1, y, x2, y, 1, "red");
draw((x1 + x2) * 0.5, inner.top + (H - Num[i].Q3 / y1 * H),
(x1 + x2) * 0.5, inner.top + (H - Num[i].U / y1 * H));
tot = Num[i].D / y1 * H;
y = inner.top + (H - tot);
draw(x1, y, x2, y);
draw((x1 + x2) * 0.5, inner.top + (H - Num[i].Q1 / y1 * H),
(x1 + x2) * 0.5, inner.top + (H - Num[i].D / y1 * H));
}
}
function Initialize() {
// ...
}
</script>
</body>
</html>
代码中未包含Initialize初始化数据部分。
采用的一个矩形块+几条线段来确定每一个box。朴素实现该过程。