天天看点

20210205源码解读-Bokeh的箱线图(boxplot)绘制

源码链接:http://docs.bokeh.org/en/latest/docs/gallery/boxplot.html?highlight=boxplot

效果:

20210205源码解读-Bokeh的箱线图(boxplot)绘制

首先需要回顾一下箱线图的基本知识。箱形图(英文:Box plot),又称为盒须图、盒式图、盒状图或箱线图,是一种用作显示一组数据分散情况资料的统计图。因型状如箱子而得名。此图中之盒子之外,也常会有线条在上下四分位数之外延伸出去,像是胡须,因此也称为盒须图。离群值会有时会画成是个别的点。(维基百科)

(图示:来自highchart,https://www.highcharts.com.cn/docs/box-plot-series/)

箱线图中一个箱体包含五个值:上边缘(最大观测值或样品最大值)、上四分位数(Q3)、中位数(Q2)、下四分位数(Q1)和下边缘(最小观测值或样品最小值)。另外在箱体外还可以用圆形点表示异常值,下面是箱线图组成部分示意图:

20210205源码解读-Bokeh的箱线图(boxplot)绘制

在 Highcharts 中,箱线图的箱体由主箱体(Box)、中位线(median)、颈部(Stem)和须线(Whishker)组成,异常值是用散点的形式展现,下面是示意图:

20210205源码解读-Bokeh的箱线图(boxplot)绘制

下面是源码解读:

"""
绘制箱线图
"""
import numpy as np
import pandas as pd
from bokeh.plotting import figure, output_file, show

# 生成六个不同类别的合成时间序列generate some synthetic time series for six different categories
cats = list("abcdef")  # 可以转化成列表
yy = np.random.randn(2000)  # 返回一个或一组样本,具有标准正态分布
g = np.random.choice(cats, 2000)  # 从cat中随机抽取字符,并组成指定大小(2000)的数组

for i, l in enumerate(cats):
    yy[g == l] += i // 2  # 在0,1,2周围分别分配了一组随机数,即a,b在0附近,c,d在1附近,e,f在2附近
df = pd.DataFrame(dict(score=yy, group=g))  # 转换为dataframe,有score和group两列


# 找到每个类别的四分位数和IQR(四分位距)。find the quartiles and IQR for each category
groups = df.groupby('group')
q1 = groups.quantile(q=0.25)  # 对分组对象做quantile分位数分析,q=0.25为第一四分位数
q2 = groups.quantile(q=0.5)  # 中位数(第二四分位数)
q3 = groups.quantile(q=0.75)  # 第三四分位数
iqr = q3 - q1  # 四分位距
upper = q3 + 1.5*iqr  # 最大值区间
lower = q1 - 1.5*iqr  # 最小值区间

# 找出每个类别的异常值 find the outliers for each category
def outliers(group):
    cat = group.name
    return group[(group.score > upper.loc[cat]['score']) | (group.score < lower.loc[cat]['score'])]['score']
out = groups.apply(outliers).dropna()

# 找出异常值的坐标 prepare outlier data for plotting, we need coordinates for every outlier.
if not out.empty:
    outx = []
    outy = []
    for keys in out.index:
        outx.append(keys[0])
        outy.append(out.loc[keys[0]].loc[keys[1]])

p = figure(tools="", background_fill_color="#efefef", x_range=cats, toolbar_location=None)

# if no outliers, shrink lengths of stems to be no longer than the minimums or maximums
qmin = groups.quantile(q=0.00)  # 最小值
qmax = groups.quantile(q=1.00)  # 最大值
upper.score = [min([x,y]) for (x,y) in zip(list(qmax.loc[:,'score']),upper.score)]
lower.score = [max([x,y]) for (x,y) in zip(list(qmin.loc[:,'score']),lower.score)]

# 绘制颈部 stems
p.segment(cats, upper.score, cats, q3.score, line_color="black")
p.segment(cats, lower.score, cats, q1.score, line_color="black")

# 绘制箱体 boxes
p.vbar(cats, 0.7, q2.score, q3.score, fill_color="#E08E79", line_color="black")
p.vbar(cats, 0.7, q1.score, q2.score, fill_color="#3B8686", line_color="black")

# 绘制箱须whiskers (almost-0 height rects simpler than segments)
p.rect(cats, lower.score, 0.2, 0.01, line_color="black")
p.rect(cats, upper.score, 0.2, 0.01, line_color="black")

# 绘制异常值 outliers
if not out.empty:
    p.circle(outx, outy, size=6, color="#F38630", fill_alpha=0.6)

p.xgrid.grid_line_color = None
p.ygrid.grid_line_color = "white"
p.grid.grid_line_width = 2
p.xaxis.major_label_text_font_size="16px"

output_file("boxplot.html", title="boxplot.py example")

show(p)