(特别篇)统计学的陷阱区,用资料绘制盒须—爬虫D3做成D3(下)

前言

本日主要内容包含另一个网路撷取资料方式Convert HTML Tables To JSON、谈及统计方法中位数的意义、盒须图介绍与绘制并浅谈其意义。

浅谈Convert HTML Tables To JSON

昨天使用了puppteer来获取网页的元素,今天使用另一项工具叫做Convert HTML Tables To JSON
常常在维基百科当中看到表格,想要撷取资料除了可以用爬虫或是将其html复制起来自己撰写程序码来剖析以外,这次介绍的工具可以使用滑鼠操作来获取网页的<table>内容将其转换成JSON档案。

撷取Html档案

以维基百科为例与昨天爬虫D3做成D3(上)文章提到获取网页元素内容副标题的部分一样,开启开发者人员工具找到维基百科的该表格选取,这次要进行的操作是滑鼠右键→Copy→CopyouterHTML

如下图

转换成JSON

Convert HTML Tables To JSON网站按下Ctrl+V将刚刚复制的HTML贴到HTML Code的下方空白处
如下图

网页滚动至底下就可以看到它就会自动帮你转成JSON格式

如下图

更改JSON物件的key值

但是通常有时候JSON里面的objectkey不是你想要的名称该怎麽办?
我们回到刚刚的找到<th>表头的地方把原先的洲别改成你想要的名称

改完名称後如下图

HTML Table转换到JSON完成

最後查看转换後的JSON的呈现状况
如下图

确定没有问题之後就可以按下底下的Download Result下载下来了

洲—维基百科
Convert HTML Tables To JSON

中位数的重要性

我们国小的数学就有教导平均数的概念,假设已知班上全部人的身高,我们将每个人的身高加总除以人数就可以得到平均身高,平均数用在人的身高可能有其数字的意义,也许仅有平均数可以猜想该班每位学生的身高,例如平均身高是150公分,班上有五个人,每个人可能可以想像是140、145、150、155、160公分,你不太可能会想像1、100、150、200、299,毕竟身高1公分和身高299公分的人其数值显然不贴近现实生活人的身高,这也是常态分布模型被广泛使用在各个领域的原因之一。

倘若今天用全国人们的资产上面的话,可能就不能反映现实情况。假设全国的平均资产是100万美元的情况,若陷入统计数字的陷阱,可能会像:「哇!这个国家人民平均的资产有100万美元,人民应该过得不差吧?」。但实际的情况是如果资产前百分之一的人民可能是1兆美元的话,由於他们的资产太高了和资产不到2万美元的人民平均後就拉高了整体平均数,显然资产这个资料不太适合使用平均数来显示其意义。

因此中位数的意义就显得其重要,中位数意义表示假设有一组数列,由小到排到大在中间的数字,换句话说即便薪水最富有的人资产是是10兆美元也不影响中位数的值。

另外中位数是得出第百分之50的数,想要得知资料分布的情况还有一项统计名词称为百分位距有兴趣的可以参阅百分位距维基百科

百分位距wiki

在d3统计API当中也有中位数和百分位距的地方叫做
d3.quantiled3.median
如下图

d3StatisticsAPI

盒须图介绍

盒须图又称作箱型图(Box plot)可以用来表示资料分布的情形,能够简单得知群体之间的离散程度
参见以下整理出的表格

名词 说明
Q1 将一组数据由小到大排列排在1/4的地方又称第一四分位数
Q2 将一组数据由小到大排列排在2/4的地方又称第二四分位数,也称中位数
Q3 将一组数据由小到大排列排在3/4的地方又称第三四分位数
IQR 由Q3-Q1所计算出来的值,称为四分位距interQuantileRange
最大值 Q1向下延伸1.5倍的距离,使用这种计算方式是要凸显(或排除)离群值(或异常值)
最小值 Q3向上延伸1.5倍的距离,使用这种计算方式是要凸显(或排除)离群值 (或异常值)

有兴趣查看更多的话请参见维基百科盒须图

盒须图wiki

绘制d3Js标签的盒须图

接下来接续昨天得到的资料我们用来绘制出其盒须图

得出Q1、Q2、Q3值

接下来我们将昨天的资料里面的文章字数算出q1的数值,中位数和q3的数值
具体程序码如下

let dataSort = d3.sort(data,(a,b) => d3.ascending(a.articleStrNum,b.articleStrNum));
let q1 = d3.quantile(dataSort,.25,d=>(d.articleStrNum));
let median =d3.quantile(dataSort,.5,d=>(d.articleStrNum));
let q3 = d3.quantile(dataSort, .75,d=>(d.articleStrNum));
let interQuantileRange = q3 - q1;
let minBox = q1 - 1.5 * interQuantileRange;
let maxBox = q1 + 1.5 * interQuantileRange;

绘制盒须图的轴线

另外也在这边多加一个g群组来装盒须图的各种svg元素,其中nice()这个函数让轴的上限或者下限能够自动延展到适当的值,不会因为你domain()设定min和max轴线然後不根据轴体的等距刻度就马上截断它。

程序码如下

let scaleY =  d3.scaleLinear()
                .domain([minBox,max])
                .range([800, 0]).nice();
let axisY =  d3.axisLeft(scaleY)
            .ticks(40);
const gY = svg.append("g")
        .attr("transform",`translate(${padding},${padding})`);
        gY.call(axisY);
let boxPlotG = svg.append("g")
        .attr("transform",`translate(${padding},${padding})`);

这里我们宣告一个盒子的中心位置和宽度如下

let boxCenter = 100;
let boxWidth = 50;

boxPlotG
.append("line")
.attr("x1", boxCenter)
.attr("x2", boxCenter)
.attr("y1", scaleY(minBox) )
.attr("y2", scaleY(maxBox) )
.attr("stroke", "black");

由於盒须图仅用到y轴,水平并没有轴线,为了方便观看所以使他偏移右边一些,因此宣告箱子的中心当作偏移量,一样记得要把y1y2的资料带入比例尺转换成svg的位置,依据盒须图的定义带入最大值和最小值。

如下图

接下来我们将绘制盒须图的盒子的部分,由於一个<rect>x,y是来自於左上角作为起始点,之後再绘制高度和宽度,因此我们将刚刚的盒子中心必须向左偏移,因为盒子对称的关系所以将盒子的宽度除以2作为偏移量,另外依照盒须图的定义带入q3的值透过比例尺转换作为y的位置,其盒须图的高度就是四分位距
具体程序码如下

boxPlotG
    .append("rect")
    .attr("x", boxCenter - boxWidth/2)
    .attr("y", scaleY(q3) )
    .attr("height", (scaleY(q1)-scaleY(q3)) )
    .attr("width", boxWidth )
    .attr("stroke", "black")
    .style("fill", "#e89baa");

之後你应当可以看到如下图

接下来我们要绘制盒须图的最小值和最大值和中位数,把刚刚所算出的这三个数字带入成一个阵列,用d3的资料绑定把这三笔资料绑定到<line>上就可以简单的划出这三条线了。
其中x位置要向左偏移,其原理与刚刚的<rect>同理。
程序码如下

        boxPlotG
        .selectAll(".SML")
        .data([minBox, median, maxBox])
        .join("line")
        .attr("x1", boxCenter-boxWidth/2)
        .attr("x2", boxCenter+boxWidth/2)
        .attr("y1", function(d){ return(scaleY(d))} )
        .attr("y2", function(d){ return(scaleY(d))} )
        .attr("stroke", "black")
        .classed("SML",true);

最後应当可以看到下图

值得一提的是文章字数并没有负值,所以在此例子当中呈现最小值为负值也显得有些奇怪。

另外呈现了以文章字数的最大值(也就是平常我们认知的最大值)当做盒须图的上限和最小值当盒须图的下限的图,还有以圆点的方式把所有文章的字数分布出来做比对,圆点的程序码如下

let circleG = svg.append("g").attr("transform",`translate(${padding},${padding})`);
circleG.selectAll("circle").data(dataSort)
.join("circle")
.attr("cx","300")
.attr("cy", function(d){ 
    return scaleY(d.articleStrNum);
})
.attr("r", "5")
.attr("fill","rgba(255,0,0,.1)" );//以透明度0.1当作颜色主要是如果资料重叠的情况发生的时候可以看得出资料密集的程度
})

呈现结果如下图

小小小结语

期望本次的实作可以让大家了解资料判读时的重要性,不要若入统计陷阱区


<<:  DAY29:开启API服务(完赛)

>>:  Day29 自动合成物品的小乌龟与指令

原来Arduino开发板的 脚位与程序内数字的对应 会随着开发商不同而改变?

我本来参考的Nano脚位(连结),他板子上写的跟程序内的数字差了3号,也就是说如果板子上是D8的脚位...

前端工程学习日记第8天

有些学生提到还要多一个 clear div 来清除会把 HTML 弄脏, 这里老师也分享一个是使用 ...

[Day-02] - Annotation Modulize Introduction

Abstract Annotation的技术风格为Java 5 之後所推畅出来的新模式,并将注解区分...

DAY24 linebot完结篇

import json,ast from django.db.models.query import...

[Day10] Web 小快乐

今天又是快乐星期六滑板日,很享受在长板上滑行的感觉 你各位,我发现我昨天不知道在嗨什麽 以为昨天就是...