作者|小雨
知乎| https://zhuanlan.zhihu.com/pypcfx
简介|中途转行的数据挖掘工程师
直方图是一种可以快速展示数据概率分布的工具,直观易懂,深受数据爱好者的喜爱。平时可能会看到matplotlib、seaborn等最高级的打包库包,类似于下面的图。
这个博主会总结所有使用Python绘制直方图的方法,大致可以分为三类(详细划分为五类,参考文末总结):
纯Python实现直方图,不使用任何第三方库使用Numpy来创建直方图总结数据使用matplotlib,pandas,seaborn绘制直方图下面,我们将逐一介绍每种方法的来龙去脉。
直方图在纯Python中的实现
在准备用纯Python绘制直方图时,最简单的想法是显示每个值在报表中出现的次数。在这种情况下,使用字典来完成这个任务是非常合适的。让我们看看下面的代码是如何实现的。
>。>。>。a = ( 0,1,1,1,2,3,7,7,23)
>。>。>。defcount_elements(seq)->格言:
..." " "从“序列”中计数元素。"""
...hist = {}
...fori inseq:
...hist[i] = hist.get(i,0) + 1
...returnhist
>。>。>。counted = count_elements(a)
>。>。>。计算
{ 0: 1, 1: 3, 2: 1, 3: 1, 7: 2, 23: 1}
我们可以看到count_elements()返回一个字典,字典中出现的键都是目标列表中唯一的值,值是所有值出现的频率。用hist[i] = hist.get(i,0)+1,累加每个值的次数,每次加1。
实际上,这个功能可以通过Python标准库,即集合来完成。计数器类,兼容Pyhont字典,覆盖字典的。update()方法。
>。>。>。从集合导入计数器
>。>。>。计数=计数器(a)
>。>。>。详细叙述
计数器({ 0: 1,1: 3,3: 1,2: 1,7: 2,23: 1})
我们可以看到这种方法的结果和之前自己实现的方法是一样的,也可以通过集合来检查两种方法得到的结果是否相等。柜台
>。>。>。counted.items() == counted.items()
真正的
我们用上面的函数重新创建了一个轮子ASCII _直方图,最后通过Python的输出格式format实现了直方图的显示。代码如下:
defascii _直方图(seq)->无:
" " "水平频率表/直方图。"""
counted = count_elements(seq)
叉入(计数):
打印(' {0:5d} {1} '。格式(k,'+'* counted[k])
该函数以数值的顺序绘制,数值出现的次数用(+)表示。在字典中调用sorted()会按顺序返回一个键列表,然后就可以得到对应的counted[k]的个数。
>。>。>。随机导入
>。>。>。random.seed( 1)
>。>。>。val =[1,3,4,6,8,9,10]
>。>。>。# `vals中的数字将出现5到15次
>。>。>。freq = (random.randint( 5,15)for _ in val)
>。>。>。数据= []
>。>。>。对于f,v in (freq,val):
...data.extend([v] * f)
>。>。>。ascii _直方图(数据)
1+++++++
3++++++++++++++
4++++++
6+++++++++
8++++++
9++++++++++++
10++++++++++++
在该代码中,val中的值不重复,每个值的出现频率由我们定义,并在5到15之间随机选择。然后,使用上面封装的函数,得到纯Python版本的直方图显示。
总结:纯python实现频率表(非标准直方图),可以通过集合直接实现。计数器方法。
用Numpy实现直方图
以上是一个使用纯Python的简单直方图,但是从数学角度来说,直方图是一个从盒子到频率的映射,可以用来估计变量的概率密度函数。但是,上面的纯Python实现版本只是一个简单的频率统计,并不是真正的直方图。
因此,我们继续从上面实现的简单直方图升级。一个真实的直方图首先要把变量划分成区域(框),也就是划分成不同的区间,然后统计每个区间的观测值个数。恰好Numpy的直方图法可以做到这一点,不仅如此,也是后面要提到的matplotlib和熊猫使用的基础。
比如看一组从拉普拉斯分布中提取的浮点样本数据。该分布比标准正态分布具有更宽的尾部,并且具有两个描述性参数(位置和规模):
>。>。>。将numpy导入为np
>。>。>。np.random.seed( 444)
>。>。>。np.set_printoptions(精度= 3)
>。>。>。d = np.random .拉普拉斯(loc= 15,scale= 3,size= 500)
>。>。>。d[ :5]
数组([ 18.406,18.087,16.004,16.221,7.358])
因为这是一个连续分布,不可能把每一个单个浮点值(也就是所有无数个小数位)都标注好(因为点太多)。但是你可以把数据分成盒子,然后统计每个盒子里观测值的个数,这才是真正的直方图应该做的。
我们来看看如何使用Numpy实现直方图频率统计。
>。>。>。hist,bin_edges = np .直方图(d)
>。>。>。嘘
数组([ 1,0,3,4,4,10,13,9,2,4])
>。>。>。bin_edges
数组([ 3.217,5.199,7.181,9.163,11.145,13.127,15.109,17.091,
19.073, 21.055, 23.037])
这个结果可能不是很直观。比方说,np .直方图()默认使用10个大小相同的区间(方框),然后返回一个元组(频率,方框边界),如上图所示。需要注意的是,这个边界的数量比盒子的数量多一个,可以简单的用下面的代码来确认。
>。>。>。hist.size,bin_edges.size
(10, 11)
这就是问题所在。Numpy如何划分盒子?简单的np .直方图()就可以做到,但是我们还是不知道怎么实现。我们来解剖一下np .直方图()的内部,看看它是如何实现的(以上面提到的列表A为例)。
>。>。>。#取a的最小值和最大值。
>。>。>。first_edge,last_edge = a.min(),a.max()
>。>。>。N _ equal _ bins =默认设置10 # numpy,10盒
>。>。>。bin _ edges = NP . Lin space(start = first _ edge,stop=last_edge,
...num = n _ equal _ bins,端点=真)
...
>。>。>。bin_edges
数组([ 0。, 2.3, 4.6, 6.9, 9.2, 11.5, 13.8, 16.1, 18.4, 20.7, 23.])
解释:首先获取一个列表的最小值和最大值,然后设置默认的框数,最后使用Numpy的linspace方法进行数据分段。分箱的结果也与实际情况吻合较好。0到23等分10份,23/10,所以每份的宽度是2.3。
除了np .直方图,还有两种方法可以实现同样的功能:np.bincount()和np.searchsorted()。我们来看看代码和对比结果。
>。>。>。bcounts = np.bincount(a)
>。>。>。hist,_= np .直方图(a,range=( 0,a.max()),bin = a . max()+1)
>。>。>。np.array_equal(hist,bcounts)
真正的
>。>。>。#正在复制`集合.计数器'
>。>。>。dict((np.unique(a),bcounts[bcounts .非零()]))
{ 0: 1, 1: 3, 2: 1, 3: 1, 7: 2, 23: 1}
综上所述,直方图可以用Numpy来实现,可以直接用np .直方图()或者np.bincount()来实现。
用Matplotlib和熊猫可视化直方图
从上面的研究中,我们看到了如何使用Python的基本工具构建直方图。让我们看看如何使用更强大的Python库包来完成直方图。Matplotlib基于Numpy直方图进行了多样化封装,提供了更加完善的可视化功能。
importmatplotlib.pyplot asplt
# matplotlib . axes . axes . hist()方法的接口
n,bin,patches = plt.hist(x=d,bins = ' auto ',color= '#0504aa ',
α= 0.7,rwidth= 0.85)
plt.grid(轴= 'y ',α= 0.75)
plt.xlabel('值')
“频率”
《我自己的直方图》
plt.text( 23,45,r'$mu=15,b=3$ ')
maxfreq = n.max()
#设置y轴的上限
PLT . ylim(ymax = NP . ceil(max freq/10)* 10 ifmax freq % 10 else maxfreq+10)
之前我们定义了X轴上的分格边界和Y轴上对应的频率。不难发现我们都是手动定义盒子数量的。但是,在上面的高级方法中,我们可以通过设置bins = ' auto '来自动选择两个编写的算法中的最佳算法,并最终计算出最合适的bin数。这里,该算法的目的是选择适当的间隔(框)宽度,并生成最能代表数据的直方图。
如果使用Python的科学计算工具,可以使用熊猫的Series .直方图()并通过matplotlib.pyplot.hist()绘制输入Series的直方图,如下代码所示。
进口熊猫aspd
尺寸,比例= 1000,10
通勤= pd。系列(np.random.gamma(比例,大小=大小)** 1.5)
通勤。绘图。历史(网格=真,箱= 20,rwidth= 0.9,
color= '#607c8e ')
PLT . title(' 1000名通勤者的通勤时间')
plt.xlabel('计数')
plt.ylabel(“通勤时间”)
plt.grid(轴= 'y ',α= 0.75)
熊猫。DataFrame .直方图()的使用方式与Series相同,但会生成DataFrame数据中每一列的直方图。
综上,我们可以用Seris.plot.hist(),DataFrame.plot.hist(),matplotlib可以用matplotlib.pyplot.hist()实现直方图。
绘制核密度估计(KDE)
KDE(核密度估计)是指核密度估计。用于估计随机变量的概率密度函数,可以使数据更加平滑。
如果使用熊猫库,可以使用plot.kde()创建内核密度图。plot.kde()同时适用于Series和DataFrame数据结构。但首先我们老公做了两个不同的数据样本做对比(两个完全分布的样本):
>。>。>。#两个精确分布的样本
>。>。>。平均值= 10,20
>。>。>。stdevs = 4,2
>。>。>。dist = pd。数据帧(
...np.random.normal(loc=means,scale=stdevs,size=( 1000,2)),
...列=[ 'a ',' b'])
>。>。>。dist.agg([ 'min ',' max ',' mean ',' std'])。四舍五入(小数= 2)
有
最小值- 1.5712.46
最大25.3226.44
平均值10.1219.94
标准3.941.94
如上所述,我们生成了两组正态分布样本,并通过一些描述性统计参数对两组数据进行了简单的比较。现在,我们可以在同一个Matplotlib轴上绘制每个直方图及其对应的kde。使用熊猫的plot.kde()的好处是会自动显示所有列的直方图和kde,非常方便使用。具体代码如下:
图,ax = plt .支线剧情()
dist.plot.kde(ax=ax,图例= False,title= '直方图:A对B ')
密度=真,轴=轴
ax.set_ylabel('概率')
ax.grid(轴= 'y ')
ax.set_facecolor( '#d8dcd6 ')
综上,我们可以用Seris.plot.kde(),DataFrame.plot.kde()通过熊猫实现kde图。
使用西伯恩的完美替代品
更高级的可视化工具是Seaborn,这是一个基于matplotlib的强大工具。对于直方图,Seaborn有distplot()方法,可以同时绘制单变量分布和kde的直方图,使用非常方便。以下是实现代码(以上面生成的D为例):
importseaborn assns
sns.set_style( 'darkgrid ')
sns.distplot(d)
默认情况下,distplot方法将绘制kde,并提供拟合参数,可以根据数据的实际情况选择特殊的分布。
sns.distplot(d,fit=stats .拉普拉斯,kde= False)
注意两个数字的细微差别。第一种情况,你在估计一个未知的概率密度函数(PDF),第二种情况,你知道分布,想知道哪些参数更能描述数据。
总结:要通过seaborn实现直方图,可以使用seaborn.distplot(),而seaborn也有单独的kde绘图,seaborn.kde()。
熊猫里的其他工具
除了绘图工具,熊猫还提供了方便。value_counts()方法,用于计算非空值的直方图,并将其转换为熊猫的系列结构。示例如下:
>。>。>。将熊猫作为pd导入
>。>。>。data = NP . random . choice(NP . arange(10),size= 10000,
...p=np.linspace( 1,11,10) / 60)
>。>。>。s = pd。系列(数据)
>。>。>。s.value_counts()
91831
81624
71423
61323
51089
4888
3770
2535
1347
0170
dtype:int64
>。>。>。s.value_counts(normalize=True)。head()
90.1831
80.1624
70.1423
60.1323
50.1089
dtype:float64
另外,熊猫. cut()也是强行宁滨数据的便捷方法。比如我们有一些人的年龄数据,我们想把这些数据按年龄组分类。例子如下:
>。>。>。ages = pd。系列(
...[ 1, 1, 3, 5, 8, 10, 12, 15, 18, 18, 19, 20, 25, 30, 40, 51, 52])
>。>。>。bin =(0,10,13,18,21,np.inf) #边界
>。>。>。标签=(“儿童”、“青春期前”、“青少年”、“军龄”、“成人”)
>。>。>。group = PD . cut(age,bins = bins,labels=labels)
>。>。>。groups.value _ counts()
儿童6
成人5
青少年3
军事_年龄2
青春期前1
dtype:int64
>。>。>。pd.concat((年龄,组),axis= 1)。重命名(列={ 0:'年龄',1:'组' })
年龄层
01儿童
11个孩子
23儿童
35儿童
48儿童
510儿童
612混凝土
715teen
818teen
918teen
1019军事年龄
1120军事年龄
1225成人
1330成人
1440成人
1551模块
1652模块
除了使用方便之外,更好的是这些操作最后都是用Cython代码来完成的,运行速度也很快。
总结:其他实现直方图的方法都可以。value_counts()和熊猫. cut()。
应该用哪种方法?
到目前为止,我们已经学习了许多实现直方图的方法。但是他们的缺点是什么?如何选择他们?当然,没有一种方法可以解决所有问题,也需要根据实际情况考虑如何选择。以下是在某些情况下使用的方法的建议,仅供参考。
你的情况
推荐使用
注意
列表、元组或集合的数据结构中有明确的整数数据,您不想引入任何第三方库
标准库Collection.counter()提供了一种快速直接的频率实现方法
这只是一个频率表,并没有真正意义上的直方图的方块划分
大数组数据,而你只想计算包含方框的直方图(没有可视化,纯数学计算)
Numpy的np .直方图()和np.bincount()对于直方图的纯数学计算很有帮助
有关更多信息,请参见np.digitize()
数据存在于熊猫的系列和数据帧对象中
熊猫方法,如Series.plot.hist(),DataFrame.plot.hist(),Series.value_counts(),and cut(),Series.plot.kde()和DataFrame.plot.kde()
参考熊猫的可视化部分
从任何数据结构创建高度定制和可调的直方图
推荐使用基于np .直方图()的Pyplot.hist()函数,使用频率高,容易理解。
Matplotlib可以自定义
高级包装的设计和集成(非定制)
Seaborn的distplot()可以方便地将直方图和KDE图结合起来
高级包装
参考:https://realpython.com/python-histograms/
1.《histogram 5种方法教你用Python玩转histogram直方图》援引自互联网,旨在传递更多网络信息知识,仅代表作者本人观点,与本网站无关,侵删请联系页脚下方联系方式。
2.《histogram 5种方法教你用Python玩转histogram直方图》仅供读者参考,本网站未对该内容进行证实,对其原创性、真实性、完整性、及时性不作任何保证。
3.文章转载时请保留本站内容来源地址,https://www.lu-xu.com/tiyu/1030261.html