作者 | FesianXu
导读
在理论工作中,咱们常常会遇到一堆数据,对数据的无效剖析至为要害,而数据的散布就是一种十分重要的数据属性,须要通过适合的可视化伎俩进行剖析。本文参考[1],基于 seaborn 库介绍一些罕用的数据分布可视化办法。全文 8720 字,预计浏览工夫 22 分钟
数据的散布,咱们能够了解为是“数据的形态”。一个“完满”的数据分布,会将数据所有可能的数据点都囊括其中,因而数据的散布表征了不同数据之间的本质区别。然而现实生活的数据不可能对所有可能的数据点都进行遍历(因为通常会有有限个数据点),因而咱们通常都是在某个采样的子集中,尝试对数据本原的散布进行剖析。常见的数据分布可视化办法有以下几种:
1. 直方图(Histogram)
2. 条件直方图(Conditional Histogram)
3. 核密度估计图(Kernel Density Estimation,KDE)
4. 累积散布函数图(Empirical Cumulative Distribution Function,ECDF)
5. 箱型图(boxplot)
6. 提琴图(violin plot)
7. 二元直方图(bivariate histogram)
8. 联结概率分布曲线(Joint Distribution Plot)
9. 边缘概率分布曲线(Marginal Distribution Plot)
如 Fig 1. 所示,咱们以 penguins 数据集为例子别离进行介绍
△Fig 1. penguins 数据集,一共有 344 条数据,每条数据有 7 个维度的属性。
01 单变量直方图
单变量直方图(univariate histogram)是一种单变量的散布可视化办法,将所有数据点进行分桶,而后统计落在某个桶里的数据点的频次,以柱形图的模式将每个桶的频次绘制进去。如 Fig 1.1 所示,咱们对 penguins 数据中的 flipper_length_mm 属性进行直方图绘制。
import seaborn as sns
data = sns.load_dataset("penguins", data_home="./data/seaborn-data")
# 能够筛选不同的分桶数量 bins,或者每个桶的宽度
ret = sns.displot(data, x='flipper_length_mm', bins=20)
ret = sns.displot(data, x='flipper_length_mm', bins=50)
ret = sns.displot(data, x='flipper_length_mm', binwidth=5)
△Fig 1.1 对 penguins 数据的 flipper_length_mm
属性进行绘制直方图, 从左到右, 别离是(a)分桶数是 20,(b)分桶数 50,(c)分桶宽度是 5。
咱们发现,选取不同的分桶数量和分桶宽度对于整个散布可视化后果影响很大。分桶越多,散布越粗疏,但也越容易被某些噪声影响咱们剖析整体散布趋势,个别在理论中咱们通常会选取多个分桶数进行尝试。原始的直方图统计的是分桶中的数据频次,不同数据的总数不同因而频次并不可比,通常能够思考进行归一化解决。如 Fig 1.2 所示,通常有两种类型的归一化:密度归一化,概率归一化。密度归一化指的是所有柱形面积和为 1,概率归一化指的是所有柱形的高度和为 1。密度归一化的状况下,因为纵坐标的数值会受到横坐标数值尺度的影响,通常是不可比,而概率归一化不须要思考横坐标的数值尺度,因而通常是可比的。
ret = sns.displot(data, x='flipper_length_mm', bins=20, stat='density') # 密度归一模式的归一化
ret = sns.displot(data, x='flipper_length_mm', bins=20, stat='probability') # 概率归一模式的归一化
△Fig 1.2(a)密度归一模式的归一化;(b)概率归一模式的归一化。
咱们既能够对连续变量进行直方图统计,也能够对离散变量进行直方图统计,如 Fig 1.3 所示,咱们通过设置 shrink 参数,能够管制柱形的宽度,使得可视化成果更像是一个离散变量的直方图。
tips = sns.load_dataset('tips')
ret = sns.displot(tips, x='day', stat='probability')
ret = sns.displot(tips, x='day', stat='probability', shrink=.8)
△Fig 1.3(a)shrink=1.0 的状况, 柱形之间“肩靠肩”, 没有间隙;(b)通过设置 shrink=0.8, 能够使得离散变量的柱形之间存在距离, 看起来更“离散”一些, 与连续变量直方图有所区别。
02 条件直方图
再回到 Fig 1.2,咱们能显著地发现这个散布有显著的两个峰,这个 flipper_length_mm 代表的是企鹅的鳍肢的长度,这个属性会受到其余什么属性的影响呢?在之前的直方图中,咱们绘制的是概率分布 \(P(X) \)现在咱们须要绘制条件概率分布 \(P(X|Y)\),以考查到底是其余哪些属性影响了企鹅的鳍肢的长度。如 Fig 2.1 所示,因为笔者感觉品种,性别,和寓居的岛屿可能会影响到企鹅的鳍肢长度,我绘制了 \(P(\mathrm{flipper_length_mm | species}) \) ,\(P(\mathrm{flipper_length_mm | sex}) \),\(P(\mathrm{flipper_length_mm | island}) \) 2.1(a)能够发现企鹅的品种确实有所影响(Adelie 品种和 Gentoo 品种的企鹅的鳍肢散布显著不同),而雌性雄性企鹅的鳍肢长度都出现双峰分布,因而并不是导致 Fig 1.2 中呈现双峰分布的起因,同理,从 Fig 2.1(c)中能够发现企鹅寓居的岛屿也是导致呈现双峰的起因。这些条件散布都能够通过设置 displot()中的 hue 参数实现。
ret = sns.displot(data, x="flipper_length_mm", hue='species')
ret = sns.displot(data, x="flipper_length_mm", hue='sex')
ret = sns.displot(data, x="flipper_length_mm", hue='island')
△Fig 2.1(a)-(c)是不同的条件散布直方图。
然而居住地会影响企鹅的鳍肢生长发育,这一点比拟奇怪,可能并不是一个间接起因,兴许是不同岛屿寓居的企鹅品种不同导致的?咱们能够绘制 \(P(\mathrm{island|species}) \),如 Fig 2.2 所示,其中的(a)是个别模式的条件散布柱形图,咱们发现不同条件下的柱形会存在层叠,容易看不清楚,此时能够通过设置 multiple=”stack” 将其设置为 stack 模式,柱形图之间以重叠模式出现,不会存在层叠。咱们能够发现在 Fig 2.1(c)中的 Biscoe 和 Dream 岛屿出现的两个峰,来自于这两个岛屿中的两大企鹅种群——Gentoo,Chinstrap+Adelie(这两个品种的散布较为靠近)散布导致。也就是说,鳍肢和企鹅品种的关联才是实质的因果关系。
ret = sns.displot(data, x="island", hue='species')
ret = sns.displot(data, x="island", hue='species', multiple="stack", discrete=False
△Fig 2.2(a)个别模式的条件散布绘制,有时候因为柱形图的层叠,容易看不出分明,此时能够采纳 stack 模式,如(b)所示,此时不同柱形之间不会有层叠。
咱们在 Fig 1.2 中说到过直方图的归一化,因为 Fig 1.2 中是单变量的散布归一化,因而体现到图中只是纵坐标的尺度变动,而整个图的形态是不会有变动的。在条件直方图中,咱们能够将 common_norm 设置为 False,此时进行归一化会将不同条件下的条件散布进行独立的归一化,如 Fig 2.3 所示,其中(a)和(b)只有纵坐标上的区别,而(c)是将不同条件下的条件散布进行各自的归一化。
ret = sns.displot(data, x="flipper_length_mm", hue='species')
ret = sns.displot(data, x="flipper_length_mm", hue='species', stat="probability")
ret = sns.displot(data, x="flipper_length_mm", hue='species', stat="probability", common_norm=False)
△Fig 2.3(a)条件散布原图;(b)对条件散布进行归一化,不思考不同条件下的区别;(c)进行不同条件下的别离归一化。
03 核密度估计曲线
直方图对数据进行分箱后,统计每个箱子中的数据点频次,因而绘制进去的直方图是离散的柱形图,即使数据是连续型数据。有什么办法能够更好地体现数据的间断性质呢?核密度估计(Kernel Density Estimation,KDE)是一种可行的办法,咱们假如每个数据点都是对数据分布的一次随机采样,采样自均值为察看值 \(x_i \)方差为 \(\sigma^2 \)如公式 (3-1) 所示,咱们对所有数据点的核密度估计曲线进行叠加,失去整个数据分布的核密度估计曲线。咱们的核散布通常能够采纳高斯分布,如公式 (3-2) 所示。
$$ \hat{f}{\sigma^2}(x) = \dfrac{1}{n}\sum{i=1}^{n} K_{\sigma^2}(x-x_i) $$
(3-1)
$$K_{\sigma^2}(x) = \dfrac{1}{\sqrt{2}{\sigma^2}}exp(-x^2/2{\sigma^2} )$$
(3-2)
如 Fig 3.1 所示,在给定了 6 个察看值,绘制出的直方图如 Fig 3.1(a)所示,如 Fig 3.1(b)所示,其中的红色曲线示意每个样本的高斯核密度估计,叠加起来失去蓝色曲线,为整个数据分布的核密度估计曲线。
△Fig 3.1(a)直方图;(b)对每个样本进行核密度估计,而后叠加失去数据分布的拟合曲线。
回到咱们的 seaborn,咱们通过设置参数 kind=”kde” 进行设置,同时能够通过 bw_adjust 管制其方差,咱们通常把此处的方差称之为带宽(bandwidth)。如 Fig 3.2 所示,咱们发现不同的带宽下,核密度估计曲线的形态天差地别,Fig 3.2(b)中,过小的带宽能够看到散布的更多细节,然而也会收到更多数据噪声的影响,呈现过多的毛刺。如 Fig 3.2(c)所示,过大的带宽会使得曲线过于平滑,使得一些散布细节被拆穿了,比方其中的双峰分布就被平滑得看不出来了。
sns.displot(data, x="flipper_length_mm", kind="kde", bw_adjust=1)
sns.displot(data, x="flipper_length_mm", kind="kde", bw_adjust=0.25)
sns.displot(data, x="flipper_length_mm", kind="kde", bw_adjust=1.5)
△Fig 3.2 不同的带宽参数对于核密度估计函数曲线的影响。
当然,核密度估计也能够在条件散布的状况下应用,如 Fig 3.3 所示,同样有几种不同的参数配置曲线的显式成果。
sns.displot(data, x="flipper_length_mm", hue="species", kind="kde")
sns.displot(data, x="flipper_length_mm", hue="species", kind="kde", multiple="stack")
sns.displot(data, x="flipper_length_mm", hue="species", kind="kde", fill=True)
△Fig 3.3(a)条件散布下的核密度估计曲线;(b)stack 模式的条件散布核密度估计曲线;(c)填充曲线下面积的条件散布核密度估计曲线。
能够将直方图和核密度估计曲线绘制在同一张图中以便于剖析,如 Fig 3.4 所示。
sns.displot(data, x="flipper_length_mm", kde=True)
sns.displot(data, x="flipper_length_mm", hue="species", kde=True)
△Fig 3.4 将直方图和核密度估计曲线绘制在同一张图中。
04 箱形图
直方图和核密度估计都太“重”了,很多时候在刚接触某个数据集的时候,一些统计性指标就足够让咱们对这份数据有足够的理解。罕用的统计指标有:中位数(50 分位线数),均值,方差,25 分位线数,75 分位线数等,而这些指标大多数状况能够从箱型图(Boxplot)中高深莫测。如 Fig 4.1 所示,一个箱型图由五根线形成,Q1 是 25 分位线,Q3 是 75 分位线,指的是将数据从低到高排序(升序),前 25% 称之为 25 分位线,前 75% 称之为 75 分位线。Q3-Q1 称之为四分位距(Inter Quartile Range,IQR),Q2 示意 50 分位线,也即是中位线,小于 Q1-1.5IQR 和 大于 Q3+1.5IQR 的数据称之为离群点。
△Fig 4.1 箱型图的几种根本分位线。
如 Fig 4.2 所示,咱们能够用 seaborn 绘制箱型图,其中用红点示意均值,能够发现 Fig 4.2(a)其实绘制了 \(P(\mathrm{flipper_length_mm|species}) \),当然也能够和 Fig 4.2(c)一样绘制单变量的箱型图,这种也是咱们最常见到的模式。如 Fig 4.2(b)所示,咱们还能够绘制 2 个条件下的箱型图,也即是 \(P(\mathrm{flipper_length_mm|species,sex}) \)通过箱型图,咱们能够十分直观地察看到不同数据分布之间的差异,是一种轻量化的数据分布分析方法。
sns.boxplot(data=data,
x="flipper_length_mm", y='species',
showmeans=True,
meanprops={"marker":"o",
"markerfacecolor":"red",
"markeredgecolor":"black",
"markersize":"5"})
sns.boxplot(data=data,
x="flipper_length_mm", y='species', hue='sex',
showmeans=True,
meanprops={"marker":"o",
"markerfacecolor":"red",
"markeredgecolor":"black",
"markersize":"5"})
sns.boxplot(data=data,
x="flipper_length_mm",
showmeans=True,
meanprops={"marker":"o",
"markerfacecolor":"red",
"markeredgecolor":"black",
"markersize":"5"})
△Fig 4.2(a)箱型图;(b)多个条件下的箱型图;(c)单变量箱型图。
05 提琴图
箱型图能够提供数据的直观印象,为了进一步剖析数据,咱们还是须要引入散布曲线。咱们心愿能够以箱型图的模式,同时把数据分布也可视化进去,这样咱们既能够复用箱型图失去的论断,而且能够进一步摸索数据分布的粗疏区别。提琴图(Violin Plot)就是为此设计的,如 Fig 5.1 所示,其将数据的核密度估计曲线以相似于箱型图的排版进行展现,其中的每条彩色竖线是每个实在的样本点数据。留神到提琴图实质是核密度估计曲线,因而如果样本数据过少其曲线是不精确的,所以通常咱们会把样本点也绘制进去(也即是彩色竖线),以判断数据数量是否会过于稠密导致 KDE 不相信。
sns.violinplot(data=data, x="flipper_length_mm", y='species', inner="stick")
sns.violinplot(data=data, x="flipper_length_mm", y='species', hue='sex', split=True, inner="stick")
sns.violinplot(data=data, x="flipper_length_mm", inner="stick")
△Fig 5.1 提琴图的不同模式,(a)提琴图;(b)两个条件下的提琴图;(c)单变量提琴图。
06 累计散布函数曲线
直方图须要抉择适合的分箱数,而 KDE 须要抉择适合的带宽,否则可能会影响数据分布的可视化成果进而影响剖析。有没有一种办法能够不必抉择任何参数就能表征数据的散布个性呢?教训累积散布函数(Empirical Cumulative Distribution Function,ECDF)兴许是一种可行的抉择。累积散布函数(Cumulative Distribution Function,CDF)\(F_X(x)\)对概率分布 \(f_X(x) \)进行积分或者求和失去,如公式(6-1)所示。当对理论数据进行解决时候,因为咱们的数据无限且来自于理论察看,因而咱们通常称之为“教训”[2],并且 ecdf 是采纳求和,而不是积分。
$$ F_X(x) = P(X \leq x) = \int_{-\infty}^{x}f_X(t) \mathrm{d}t \tag{6-1} $$
如 Fig 6.1 所示,ecdf 曲线是一个枯燥递增的曲线,其会思考每一个察看到的数据点,不须要指定其余额定的参数,如 Fig 6.1(b)所示,咱们也能够绘制条件散布下的 ecdf。ECDF 的毛病在于不如直方图和核密度估计一样直观地表征了数据分布,然而实践上 ECDF 同样能够“坍缩”到概率分布,如公式 (6-2) 所示。
$$ f_X(x) = \dfrac{\mathrm{d}F_X(x)}{\mathrm{d}x} \tag{6-2} $$
sns.displot(data, x="flipper_length_mm", kind="ecdf")
sns.displot(data, x="flipper_length_mm", hue="species", kind="ecdf")
△Fig 6.1 ECDF 曲线,(a)单变量的 ECDF 曲线;(b)条件散布下的 ECDF 曲线。
07 二元直方图
有时候咱们须要考查数据的联结概率分布,比方 \(P(\mathrm{flipper_length_mm, species}) \)此时能够绘制二元直方图。咱们能够指定 displot()的 y 参数,绘制两元变量的直方图。这种类型的直方图相似于热值图(heatmap),以色彩的深浅示意数值的大小。如 Fig 7.1(c)所示,同样能够指定 kind 参数进而绘制二元核密度估计曲线。在指定了 hue 的状况下,同样能够实现条件散布的绘制。
sns.displot(data, x="flipper_length_mm", y="species",cbar=True)
sns.displot(data, x="bill_length_mm", y="bill_depth_mm", hue="species")
sns.displot(data, x="bill_length_mm", y="bill_depth_mm", hue="species", kind="kde")
△Fig 7.1 二元直方图以及二元核密度估计曲线。
08 联结概率分布和边缘概率分布曲线
考查二元变量的联结概率分布,采纳散点图(scatter plot)也是一种不错的抉择,如 Fig 8.1 所示,能够将 \(P(\mathrm{bill_length_mm, bill_depth_mm})\)通过散点图的模式进行可视化,直观地考查两个变量之间的相干关系。散点图下面的直方图和左边的直方图别离是 \(P(\mathrm{bill_length_mm}) \) ,\(P(\mathrm{bill_depth_mm}) \),在这种情景下,咱们称之为边缘概率分布曲线(Marginal Distribution),其计算公式见(8-1)。
$$ P(x) = \sum_{y} P(x,y) \tag{8-1} $$
咱们也能够对联结概率分布的散点图和边缘概率分布的直方图进行核密度估计,如 Fig 8.1(b)所示。
sns.jointplot(data=data, x="bill_length_mm", y="bill_depth_mm")
sns.jointplot(
data=data,
x="bill_length_mm", y="bill_depth_mm", hue="species",
kind="kde"
)
△Fig 8.1 联结概率分布与边缘概率分布的可视化。
通过应用 seaborn 的 JointGrid 性能,能够对联结概率分布和边缘概率分布的示意模式进行自定义组合(散点图,KDE,箱型图等),如 Fig 8.2 所示。
g = sns.JointGrid(data=data, x="bill_length_mm", y="bill_depth_mm")
g.plot_joint(sns.histplot)
g.plot_marginals(sns.kdeplot)
g = sns.JointGrid(data=data, x="bill_length_mm", y="bill_depth_mm")
g.plot_joint(sns.histplot)
g.plot_marginals(sns.boxplot)
g = sns.JointGrid(data=data, x="bill_length_mm", y="bill_depth_mm")
g.plot_joint(sns.scatterplot)
g.plot_marginals(sns.boxplot)
△Fig 8.2 通过 JointGrid 进行直方图,KDE,散点图,箱型图的组合。
END
参考资料:
[1]. https://seaborn.pydata.org/tutorial/distributions.html#plotti…
[2]. https://blog.csdn.net/LoseInVain/article/details/78746520,教训误差,泛化误差
举荐浏览:
数据库运维工作量间接缩小 50%,基于大模型构建智能问答零碎的技术分享
千万级高性能长连贯 Go 服务架构实际
百度搜寻 Push 个性化:新的冲破
智算让大模型触手可及
通过 Python 脚本反对 OC 代码重构实际(三):数据项应用模块接入数据通路的适配