Box plot (箱形图) 中 quartile (四分位数)原理,及python_matplotlib中Q1和Q3定义的不同
1. 首先介绍Boxplot(箱形图)的定义,这里参考:Understanding Boxplots,非常精彩的一篇介绍boxplot的博文。
该图片显示的即是一个boxplot的基本组成部分, 它由5个基本数值决定,即 最小值 (minimum); 下四分位数 (first quartile, Q1); 中值或中位数 (median), 或第二个四分位数 (second quartile, Q2); 上四分位数 (third quartile, Q3); 最大值 (maximum)。
其它定义:四分位间距 (interquartile range, IQR), 表示下四分位数Q1和上四分位数Q3的间距; 盒须 (wiskers), 上图中蓝色的延伸线,长度为1.5*IQR, 分为两段,分别是Q1延伸至minimum, Q3延伸至maximum; 离群值 (outliers), 上图中绿色的点,表示小于minimum的值和大于maximum的值。
这里需要注意,boxplot是用于显示数据分散情况的图,其中的minimum和maximum很可能不表示数据集中的最小、最大点,而是由Q1、Q3、IQR决定, 而不分布于[minimum, maximum]的点视为outlier.
2. 接下来介绍三个四分位数Q1、Q2和Q3的具体取值情况,这里部分参考:Wikipedia: Quartile 维基百科。
Q2,即median的取值比较简单,设n表示数据集中点的数目,a为该数据集的升序数组.
这里分为两种情况:
- n = 2 * i + 1, 即数据点个数为奇数 (odd number), median (Q2) = a[i]
- n = 2 * i, 即数据点个数为偶数 (even number), median (Q2) = a[i-1] * 0.5 + a[i] * 0.5
Q1和Q3的取值,根据Wikipedia: Quartile上的解释,还没有通用的取值。
上面介绍了3种主要的取值方法,需要先将点的数量n分为4种情况:
- n = 4 * i, 即数据点个数为偶数,将其平分后,每一份的个数仍是偶数;
- n = 4 * i + 2, 即数据点个数为偶数, 将其平分后,每一份的个数为奇数;
- n = 4 * i + 1, 即数据点个数为奇数,先将其去除中值Q2, 再平分后,每一份的个数为偶数;
- n = 4 * i +3, 即数据点个数为奇数,先将其去除中值Q2, 再平分后,每一份的个数为奇数。
3种方法对于Q1和Q3的具体取值为:
当n为偶数时,3种方法对于Q1和Q3的取值均相同,即先将a等分为下半 (lower half) 和上半 (upper half)(不去除用于计算中值的两个数),等分之后的数组再分别计算中值,Q1为下半的中值,Q3为上半的中值。
若 n = 4 * i, Q1 = a[i-1] * 0.5 + a[i] * 0.5; Q3 = a[3*i-1] * 0.5 + a[3*i] * 0.5
若 n = 4 * i + 2, Q1 = a[i]; Q3 = a[3*i+1]
3种方法对于Q1和Q3的取值不同体现在当n为奇数时:
Method 1:对其等分为下半和上半时,两个数组均不包含中间的那个中值,Q1为下半的中值,Q3为上半的中值:
若n = 4 * i + 1, Q1 = a[i-1] * 0.5 + a[i] * 0.5; Q3 = a[3*i] * 0.5 + a[3*i+1] * 0.5
若n = 4 * i + 3, Q1 = a[i]; Q3 = a[3*i+2]
Method 2: 对其等分为下半和上半时,两个数组均包含中间的那个中值,Q1为下半的中值,Q3为上半的中值:
若n = 4 * i + 1, Q1 = a[i]; Q3 = a[3*i]
若n = 4 * i + 3, Q1 = a[i] * 0.5 + a[i+1] * 0.5; Q3 = a[3*i+1] * 0.5 + a[3*i+2] * 0.5
Method 3: 对其等分为下半和上半时,两种情况下,一种两个数组均包含中间的那个均值,另一种两个数组均不包含中间的那个中值,使两个半数组中数值个数为偶数,Q1、Q3分别为下半和上半中间值的权值,不是中值。
若n = 4 * i + 1, 两个数组不包含中间那个中值,此时method 3和method 1相似,不过权值分别为0.25和0.75,不是0.5和0.5
Q1 = a[i-1] * 0.25 + a[i] * 0.75; Q3 = a[3*i] * 0.75 + a[3*i+1] * 0.25
若n = 4 * i +3, 两个数组包含中间的那个中值,此时method 3和method 2相似,不过权值分别为0.25和0.75, 不是0.5和0.5
Q1 = a[i] * 0.75 + a[i+1] * 0.25; Q3 = a[3*i+1] * 0.25 + a[3*i+2] * 0.75
示例:
示例1: n = 8 (2*4), a = [6, 7, 15, 36, 39, 40, 41, 42]
Method 1 | Method 2 | Method 3 | |
Q1 | 11 | 11 | 11 |
Q2 | 37.5 | 37.5 | 37.5 |
Q3 | 40.5 | 40.5 | 40.5 |
示例2: n = 10 (2*4+2), a = [6, 7, 15, 36, 39, 40, 41, 42, 43, 47]
Method 1 | Method 2 | Method 3 | |
Q1 | 15 | 15 | 15 |
Q2 | 39.5 | 39.5 | 39.5 |
Q3 | 42 | 42 | 42 |
示例3: n = 9 (2*4+1), a = [6, 7, 15, 36, 39, 40, 41, 42, 43]
Method 1 | Method 2 | Method 3 | |
Q1 | 11 | 15 | 13 |
Q2 | 39 | 39 | 39 |
Q3 | 41.5 | 41 | 41.25 |
示例4: n = 11 (2*4+3), a = [6, 7, 15, 36, 39, 40, 41, 42, 43, 47, 49]
Method 1 | Method 2 | Method 3 | |
Q1 | 15 | 25.5 | 9 |
Q2 | 40 | 40 | 40 |
Q3 | 43 | 42.5 | 42.75 |
3. Python 中matplotlib.pyplot中包含的boxplot()函数可直接用于画boxplot箱形图。
定义,及参数的官方文档参考:matplotlib.pyplot.boxplot
不过,如第二节介绍Q1、Q2和Q3取值时说明,目前对于Q1和Q3,没有通用的取值标准,这里我用实际的示例进行实验,发现其对于Q1和Q3的取值,和上述3种方法均不同。
假设定义如上,n表示数据点的数目,a表示升序数组:
若n = 4 * i, Q1 = a[i-1] * 0.25 + a[i] * 0.75; Q3 = a[3*i-1] * 0.75 + a[3*i] * 0.25
此时,Q1和Q3和3种Method的取的元素相同,但权值不是0.5和0.5。
示例:
import numpy as np
import matplotlib.pyplot as plt
#A2 = [6, 7, 15, 36, 39, 40, 41, 42]
A2 = [1, 2, 3, 4, 5, 6, 7, 8]
#n = 8
A2_sort = sorted(A2)
plt.boxplot(A2, whis = 1.5000)
median = np.median(A2)
plt.plot([1, 2], [median, median], lw = 0.40)
plt.plot([0, 1], [A2_sort[1] * 0.25 + A2_sort[2] * 0.75, A2_sort[1] * 0.25 + A2_sort[2] * 0.75], lw = 0.80, linestyle= ':')
plt.plot([0, 1], [A2_sort[5] * 0.75 + A2_sort[6] * 0.25, A2_sort[5] * 0.75 + A2_sort[6] * 0.25], lw = 0.80, linestyle= ':')
plt.plot([1, 2], [A2_sort[1] * 0.50 + A2_sort[2] * 0.50, A2_sort[1] * 0.50 + A2_sort[2] * 0.50], lw = 0.75, linestyle= '-')
plt.plot([1, 2], [A2_sort[5] * 0.50 + A2_sort[6] * 0.50, A2_sort[5] * 0.50 + A2_sort[6] * 0.50], lw = 0.75, linestyle= '-')
#plt.grid(True, linestyle = '--')
#plt.savefig('anita.png', dpi = 600, bbox_inches = 'tight')
plt.show()
运行结果:
明显,左边由自己实验验证的权值,即虚线求出的Q1和Q3和真正由boxplot()画出的Q1和Q3是吻合的 ,和上述3种方法的在n为偶数的相同情况,即右边实线画出的Q1和Q3和boxplot()画出的不同。
若 n = 4 * i + 2 , Q1 = a[i] * 0.75 + a[i+1] * 0.25; Q3 = a[3*i] * 0.25 + a[3*i+1] * 0.75
示例:
import numpy as np
import matplotlib.pyplot as plt
A2 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
#n = 10
A2_sort = sorted(A2)
plt.boxplot(A2, whis = 1.5000)
median = np.median(A2)
plt.plot([1, 2], [median, median], lw = 0.40)
plt.plot([0, 1], [A2_sort[2] * 0.75 + A2_sort[3] * 0.25, A2_sort[2] * 0.75 + A2_sort[3] * 0.25], lw = 0.80, linestyle= ':')
plt.plot([0, 1], [A2_sort[6] * 0.25 + A2_sort[7] * 0.75, A2_sort[6] * 0.25 + A2_sort[7] * 0.75], lw = 0.80, linestyle= ':')
plt.plot([1, 2], [A2_sort[2], A2_sort[2]], lw = 0.75, linestyle= '-')
plt.plot([1, 2], [A2_sort[7], A2_sort[7]], lw = 0.75, linestyle= '-')
#plt.grid(True, linestyle = '-.')
#plt.savefig('Bob.png', dpi = 600, bbox_inches = 'tight')
plt.show()
运行结果:
左边由虚线画出的线,即自己实验验证的权值,和boxplot()画出的Q1和Q3吻合;而右边,上述3种方法画出的实线,和实际结果有偏差。
若 n = 4 * i + 1, Q1 = a[i]; Q3 = a[3*i]
此时的Q1和Q3取值和Method 2相同。
示例:
import numpy as np
import matplotlib.pyplot as plt
A2 = [1, 2, 3, 4, 5, 6, 7, 8, 9]
#n = 9
A2_sort = sorted(A2)
plt.boxplot(A2, whis = 1.5000)
median = np.median(A2)
plt.plot([1, 2], [median, median], lw = 0.40)
plt.plot([0, 1], [A2_sort[2], A2_sort[2]], lw = 0.80, linestyle = ':')
plt.plot([0, 1], [A2_sort[6], A2_sort[6]], lw = 0.80, linestyle = ':')
plt.plot([1, 2], [A2_sort[1] * 0.50 + A2_sort[2] * 0.50, A2_sort[1] * 0.50 + A2_sort[2] * 0.50], lw = 0.75, linestyle= '-', color='r') #Method 1
plt.plot([1, 2], [A2_sort[6] * 0.50 + A2_sort[7] * 0.50, A2_sort[6] * 0.50 + A2_sort[7] * 0.50], lw = 0.75, linestyle= '-', color = 'r')
plt.plot([1, 2], [A2_sort[2], A2_sort[2]], lw = 0.75, linestyle= '-', color = 'k') #Method 2
plt.plot([1, 2], [A2_sort[6], A2_sort[6]], lw = 0.75, linestyle= '-', color = 'k')
plt.plot([1, 2], [A2_sort[1] * 0.25 + A2_sort[2] * 0.75, A2_sort[1] * 0.25 + A2_sort[2] * 0.75], lw = 0.75, linestyle= '-', color = 'b') #Method 3
plt.plot([1, 2], [A2_sort[6] * 0.75 + A2_sort[7] * 0.25, A2_sort[6] * 0.75 + A2_sort[7] * 0.25], lw = 0.75, linestyle= '-', color = 'b')
#plt.grid(True, linestyle = '--')
#plt.savefig('claire.png', dpi = 600, bbox_inches = 'tight')
plt.show()
运行结果:
右边画出的实线,红、黑蓝分别是Method 1、Method 2、Method 3画出的Q1和Q3的值;左边虚线是自己实验验证的结果, 由对比可知,黑色的实线以及虚线,和boxplot()画出的Q1和Q3吻合,此时Q1和Q3的取值是由Method 2决定的。
若 n = 4 * i +3, Q1 = a[i] * 0.5 + a[i+1] * 0.5; Q3 = a[3*i+1] * 0.5 + a[3*i+2] * 0.5
此时的Q1和Q3取值和Method 2相同。
示例:
import numpy as np
import matplotlib.pyplot as plt
A2 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
#n = 11
A2_sort = sorted(A2)
plt.boxplot(A2, whis = 1.5000)
median = np.median(A2)
plt.plot([1, 2], [median, median], lw = 0.40)
plt.plot([0, 1], [A2_sort[2] * 0.50 + A2_sort[3] * 0.50, A2_sort[2] * 0.50 + A2_sort[3] * 0.50], lw = 0.80, linestyle= ':')
plt.plot([0, 1], [A2_sort[7] * 0.50 + A2_sort[8] * 0.50, A2_sort[7] * 0.50 + A2_sort[8] * 0.50], lw = 0.80, linestyle= ':')
plt.plot([1, 2], [A2_sort[2], A2_sort[2]], lw = 0.75, linestyle= '-', color = 'r') #Method 1
plt.plot([1, 2], [A2_sort[8], A2_sort[8]], lw = 0.75, linestyle= '-', color = 'r')
plt.plot([1, 2], [A2_sort[2] * 0.50 + A2_sort[3] * 0.50, A2_sort[2] * 0.50 + A2_sort[3] * 0.50], lw = 0.75, linestyle= '-', color='k') #Method 1
plt.plot([1, 2], [A2_sort[7] * 0.50 + A2_sort[8] * 0.50, A2_sort[7] * 0.50 + A2_sort[8] * 0.50], lw = 0.75, linestyle= '-', color = 'k')
plt.plot([1, 2], [A2_sort[2] * 0.75 + A2_sort[3] * 0.25, A2_sort[2] * 0.75 + A2_sort[3] * 0.25], lw = 0.75, linestyle= '-', color = 'b') #Method 3
plt.plot([1, 2], [A2_sort[7] * 0.25 + A2_sort[8] * 0.75, A2_sort[7] * 0.25 + A2_sort[8] * 0.75], lw = 0.75, linestyle= '-', color = 'b')
#plt.grid(True, linestyle = '--')
#plt.savefig('david.png', dpi = 600, bbox_inches = 'tight')
plt.show()
运行结果:
右边画出的实线,红、黑蓝分别是Method 1、Method 2、Method 3画出的Q1和Q3的值;左边虚线是自己实验验证的结果, 由对比可知,黑色的实线以及虚线,和boxplot()画出的Q1和Q3吻合,此时Q1和Q3的取值是由Method 2决定的。
4. 对matplotlib.pyplot中boxplot()总结:
n的数量 | Q1 | Q3 | 和3种Method关系 |
4 * i |
a[i-1] * 0.25 + a[i] * 0.75 |
a[3*i-1] * 0.75 + a[3*i] * 0.25 |
无,权值改变 |
4 * i + 2 |
a[i] * 0.75 + a[i+1] * 0.25 |
a[3*i] * 0.25 + a[3*i+1] * 0.75 |
无,权值改变 |
4* i +1 | a[i] | a[3*i] | Method 2 |
4 * i +3 |
a[i] * 0.5 + a[i+1] * 0.5 |
a[3*i+1] * 0.5 + a[3*i+2] * 0.5 |
Method 2 |
5. 示例:
这里用的示例是基于官方文档给出的Boxplot Demo
n = 100, 50个数为(0, 100)的随机分布,30个数为(0,50)的随机分布,10个数为(0, 200)的随机分布,10个数为(-100, 0)的随机分布,画出其箱形图。
# n较大的示例
import numpy as np
import matplotlib.pyplot as plt
import time
np.random.seed(int(time.time()))
spread = np.random.rand(50) * 100
center = np.random.rand(30) * 50
filter_high = np.random.rand(10) * 100 + 100
filter_low = np.random.rand(10) * 100 - 100
data = np.concatenate((spread, center, filter_high, filter_low))
fig1, ax1 = plt.subplots()
ax1.set_title('Box Plot Demo')
plt.ylim(-101, 201)
ax1.boxplot(data, showfliers = True, whis = 1.50000)
####Verifing the data
data_sort = sorted(data)
Q1 = data_sort[24] * 0.25 + data_sort[25] * 0.75
Q3 = data_sort[74] * 0.75 + data_sort[75] * 0.25
IQR = Q3 - Q1
Minimum = Q1 - 1.50000 * IQR
Maximum = Q3 + 1.5000 * IQR
median = np.median(data)
outlier = np.array([data_sort[0], data_sort[-1]])
for i in range(len(data)): # 求出minimum, 即大于等于Q1-1.5*IQR最小的数值;
if(data_sort[i] >= Minimum ):
minimum = data_sort[i]
break
for i in range(len(data)): # 求出maximum, 即小于等于Q3+1.5*IQR最大的数值。
if(data_sort[::-1][i] <= Maximum):
maximum_num = i
maximum = data_sort[::-1][i]
break
plt.plot([0, 1], [median, median], lw = 0.50)
plt.plot([0, 1], [Q1, Q1], lw = 0.80, linestyle = ':')
plt.plot([0, 1], [Q3, Q3], lw = 0.80, linestyle = ':')
Q1_M = data_sort[24] * 0.50 + data_sort[25] * 0.50 # value adopted in 3 methods
Q3_M = data_sort[74] * 0.50 + data_sort[75] * 0.50
plt.plot([1, 2], [Q1_M, Q1_M], lw = 0.75, linestyle = '-')
plt.plot([1, 2], [Q3_M, Q3_M], lw = 0.75, linestyle = '-')
plt.plot([0, 2], [minimum, minimum], lw = 0.50)
plt.plot([0, 2], [maximum, maximum], lw = 0.50)
#plt.grid(True, linestyle = '-.')
#plt.savefig("plot_100.png", dpi = 600, bbox_inches = 'tight')
plt.show()
一次的运行结果:
需要注意,由于这样的随机取值下,权值是(0.5, 0.5)(右实线)或(0.25, 0.75)(左虚线)相差不大, 但如果采用错误的权值,有可能造成求minimum和maximum错误。