python直⽅图的绘制⽅法全解_5种⽅法教你⽤Python玩转
histogram直⽅图
阅读本⽂需要 10 分钟
直⽅图是⼀个可以快速展⽰数据概率分布的⼯具,直观易于理解,并深受数据爱好者的喜爱。⼤家平时可能见到最多就是
matplotlib,seaborn 等⾼级封装的库包,类似以下这样的绘图。
本篇博主将要总结⼀下使⽤Python绘制直⽅图的所有⽅法,⼤致可分为三⼤类(详细划分是五类,参照⽂末总结):纯Python实现直⽅图,不使⽤任何第三⽅库
使⽤Numpy来创建直⽅图总结数据
使⽤matplotlib,pandas,seaborn绘制直⽅图
下⾯,我们来逐⼀介绍每种⽅法的来龙去脉。
纯Python实现histogram
当准备⽤纯Python来绘制直⽅图的时候,最简单的想法就是将每个值出现的次数以报告形式展⽰。这种情况下,使⽤ 字典 来完成这个任务是⾮常合适的,我们看看下⾯代码是如何实现的。>>> a = (0, 1, 1, 1, 2, 3, 7, 7, 23)
>>> def count_elements(seq) -> dict:
"""Tally elements from `seq`."""
... hist = {}
... for i in seq:
... hist[i] = (i, 0) + 1
... return hist
>>> counted = count_elements(a)
>>> counted
{0: 1, 1: 3, 2: 1, 3: 1, 7: 2, 23: 1}
我们看到,count_elements() 返回了⼀个字典,字典⾥出现的键为⽬标列表⾥⾯的所有唯⼀数值,⽽值为所有数值出现的频率次数。
hist[i] = (i, 0) + 1 实现了每个数值次数的累积,每次加⼀。
实际上,这个功能可以⽤⼀个Python的标准库 collection.Counter 类来完成,它兼容Pyhont 字典并覆盖了字典的 .update() ⽅法。>>> from collections import Counter
>>> recounted = Counter(a)
>>> recounted
Counter({0: 1, 1: 3, 3: 1, 2: 1, 7: 2, 23: 1})
可以看到这个⽅法和前⾯我们⾃⼰实现的⽅法结果是⼀样的,我们也可以通过 collection.Counter 来检验两种⽅法得到的结果是否相等。>>> recounted.items() == counted.items()
True
我们利⽤上⾯的函数重新再造⼀个轮⼦ ASCII_histogram,并最终通过Python的输出格式format来实现直⽅图的展⽰,代码如下:def ascii_histogram(seq) -> None:
"""A horizontal frequency-table/histogram plot."""
counted = count_elements(seq)
for k in sorted(counted):
print('{0:5d} {1}'.format(k, '+' * counted[k]))
这个函数按照数值⼤⼩顺序进⾏绘图,数值出现次数⽤ (+) 符号表⽰。在字典上调⽤ sorted() 将会返回⼀个按键顺序排列的列表,然后就可以获取相应的次数 counted[k] 。>>> import random
>>> random.seed(1)
>>> vals = [1, 3, 4, 6, 8, 9, 10]
>>> # `vals` ⾥⾯的数字将会出现5到15次
>>> freq = (random.randint(5, 15) for _ in vals)
>>> data = []
>>> for f, v in zip(freq, vals):
... d([v] * f)
>>> ascii_histogram(data)
1 +++++++
3 ++++++++++++++
4 ++++++
6 +++++++++
8 ++++++
9 ++++++++++++
10 ++++++++++++
这个代码中,vals内的数值是不重复的,并且每个数值出现的频数是由我们⾃⼰定义的,在5和15之间随机选择。然后运⽤我们上⾯封装的函数,就得到了纯Python版本的直⽅图展⽰。
总结:纯python实现频数表(⾮标准直⽅图),可直接使⽤collection.Counter⽅法实现。
使⽤Numpy实现histogram
以上是使⽤纯Python来完成的简单直⽅图,但是从数学意义上来看,直⽅图是分箱到频数的⼀种映射,它可以⽤来估计变量的概率密度函数的。⽽上⾯纯Python实现版本只是单纯的频数统计,不是真正意义上的直⽅图。
因此,我们从上⾯实现的简单直⽅图继续往下进⾏升级。⼀个真正的直⽅图⾸先应该是将变量分区域(箱)的,也就是分成不同的区间范围,然后对每个区间内的观测值数量进⾏计数。恰巧,Numpy的直⽅图⽅法就可以做到这点,不仅仅如此,它也是后⾯将要提到的matplotlib和pandas使⽤的基础。
举个例⼦,来看⼀组从拉普拉斯分布上提取出来的浮点型样本数据。这个分布⽐标准正态分布拥有更宽的尾部,并有两个描述参数(location和scale):>>> import numpy as np
>>> np.random.seed(444)
>>> np.set_printoptions(precision=3)
>>> d = np.random.laplace(loc=15, scale=3, size=500)
>>> d[:5]
array([18.406, 18.087, 16.004, 16.221, 7.358])
由于这是⼀个连续型的分布,对于每个单独的浮点值(即所有的⽆数个⼩数位置)并不能做很好的标签(因为点实在太多了)。但是,你可以将数据做 分箱 处理,然后统计每个箱内观察值的数量,这就是真正的直⽅图所要做的⼯作。
下⾯我们看看是如何⽤Numpy来实现直⽅图频数统计的。>>> hist, bin_edges = np.histogram(d)
>>> hist
array([ 1, 0, 3, 4, 4, 10, 13, 9, 2, 4])
>>> bin_edges
array([ 3.217, 5.199, 7.181, 9.163, 11.145, 13.127, 15.109, 17.091,
19.073, 21.055, 23.037])
这个结果可能不是很直观。来说⼀下,np.histogram() 默认地使⽤10个相同⼤⼩的区间(箱),然后返回⼀个元组(频数,分箱的边界),如上所⽰。要注意的是:这个边界的数量是要⽐分箱数多⼀个的,可以简单通过下⾯代码证实。>>> hist.size, bin_edges.size
(10, 11)
那问题来了,Numpy到底是如何进⾏分箱的呢?只是通过简单的 np.histogram() 就可以完成了,但具体是如何实现的我们仍然全然不知。下⾯让我们来将 np.histogram() 的内部进⾏解剖,看看到底是如何实现的(以最前⾯提到的a列表为例)。>>> # 取a的最⼩值和最⼤值
>>> first_edge, last_edge = a.min(), a.max()
>>> n_equal_bins = 10 # NumPy得默认设置,10个分箱
>>> bin_edges = np.linspace(start=first_edge, stop=last_edge,
... num=n_equal_bins + 1, endpoint=True)
...
>>> bin_edges
array([ 0. , 2.3, 4.6, 6.9, 9.2, 11.5, 13.8, 16.1, 18.4, 20.7, 23. ])
解释⼀下:⾸先获取a列表的最⼩值和最⼤值,然后设置默认的分箱数量,最后使⽤Numpy的 linspace
⽅法进⾏数据段分割。分箱区间的结果也正好与实际吻合,0到23均等分为10份,23/10,那么每份宽度为2.3。
除了np.histogram之外,还存在其它两种可以达到同样功能的⽅法:np.bincount() 和 np.searchsorted(),下⾯看看代码以及⽐较结果。>>> bcounts = np.bincount(a)
>>> hist, _ = np.histogram(a, range=(0, a.max()), bins=a.max() + 1)
>>> np.array_equal(hist, bcounts)
True
>>> # Reproducing `collections.Counter`
>>> dict(zip(np.unique(a), o()]))
{0: 1, 1: 3, 2: 1, 3: 1, 7: 2, 23: 1}
总结:通过Numpy实现直⽅图,可直接使⽤np.histogram()或者np.bincount()。
使⽤Matplotlib和Pandas可视化Histogram
从上⾯的学习,我们看到了如何使⽤Python的基础⼯具搭建⼀个直⽅图,下⾯我们来看看如何使⽤更为强⼤的Python库包来完成直⽅图。Matplotlib基于Numpy的histogram进⾏了多样化的封装并提供了更加完善的可视化功能。import matplotlib.pyplot as plt
# matplotlib.axes.Axes.hist() ⽅法的接⼝
n, bins, patches = plt.hist(x=d, bins='auto', color='#0504aa',
alpha=0.7, rwidth=0.85)
plt.xlabel('Value')
plt.ylabel('Frequency')
plt.title('My Very Own Histogram')
<(23, 45, r'$\mu=15, b=3$')
maxfreq = n.max()
# 设置y轴的上限
plt.ylim(il(maxfreq / 10) * 10 if maxfreq % 10 else maxfreq + 10)
之前我们的做法是,在x轴上定义了分箱边界,y轴是相对应的频数,不难发现我们都是⼿动定义了分箱的数⽬。但是在以上的⾼级⽅法中,我们可以通过设置 bins='auto' ⾃动在写好的两个算法中择优选择并最终算出最适合的分箱数。这⾥,算法的⽬的就是选择出⼀个合适的区间(箱)宽度,并⽣成⼀个最能代表数据的直⽅图来。
如果使⽤Python的科学计算⼯具实现,那么可以使⽤Pandas的 Series.histogram() ,并通过 matplotlib.pyplot.hist() 来绘制输⼊Series的直⽅图,如下代码所⽰。import pandas as pd
size, scale = 1000, 10
commutes = pd.Series(np.random.gamma(scale, size=size) ** 1.5)
commutes.plot.hist(grid=True, bins=20, rwidth=0.9,
color='#607c8e')
plt.title('Commute Times for 1,000 Commuters')
plt.xlabel('Counts')
plt.ylabel('Commute Time')
pandas.DataFrame.histogram() 的⽤法与Series是⼀样的,但⽣成的是对DataFrame数据中的每⼀列的直⽅图。
总结:通过pandas实现直⽅图,可使⽤Seris.plot.hist(),DataFrame.plot.hist(),matplotlib实现直⽅图可以⽤
matplotlib.pyplot.hist()。
绘制核密度估计(KDE)
KDE(Kernel density estimation)是核密度估计的意思,它⽤来估计随机变量的概率密度函数,可以将数据变得更平缓。
使⽤Pandas库的话,你可以使⽤ plot.kde() 创建⼀个核密度的绘图,plot.kde() 对于 Series和DataFra
me数据结构都适⽤。但是⾸先,我们先⽣成两个不同的数据样本作为⽐较(两个正太分布的样本):>>> # 两个正太分布的样本
>>> means = 10, 20
>>> stdevs = 4, 2
>>> dist = pd.DataFrame(
... al(loc=means, scale=stdevs, size=(1000, 2)),
... columns=['a', 'b'])
>>> dist.agg(['min', 'max', 'mean', 'std']).round(decimals=2)
a b
min -1.57 12.46
max 25.32 26.44
mean 10.12 19.94
std 3.94 1.94
以上看到,我们⽣成了两组正态分布样本,并且通过⼀些描述性统计参数对两组数据进⾏了简单的对⽐。现在,我们可以在同⼀个Matplotlib轴上绘制每个直⽅图以及对应的kde,使⽤pandas的plot.kde()的好处就是:它会⾃动的将所有列的直⽅图和kde都显⽰出来,⽤起来⾮常⽅便,具体代码如下:fig, ax = plt.subplots()
dist.plot.kde(ax=ax, legend=False, title='Histogram: A vs. B')
dist.plot.hist(density=True, ax=ax)
ax.set_ylabel('Probability')
ax.set_facecolor('#d8dcd6')
总结:通过pandas实现kde图,可使⽤Seris.plot.kde(),DataFrame.plot.kde()。
使⽤Seaborn的完美替代
⼀个更⾼级可视化⼯具就是Seaborn,它是在matplotlib的基础上进⼀步封装的强⼤⼯具。对于直⽅图⽽⾔,Seaborn有 distplot() ⽅法,可以将单变量分布的直⽅图和kde同时绘制出来,⽽且使⽤及其⽅便,下⾯是实现代码(以上⾯⽣成的d为例):import seaborn as sns
sns.set_style('darkgrid')
sns.distplot(d)
distplot⽅法默认的会绘制kde,并且该⽅法提供了 fit 参数,可以根据数据的实际情况⾃⾏选择⼀个特殊的分布来对应。sns.distplot(d,
fit=stats.laplace, kde=False)注意这两个图微⼩的区别。第⼀种情况你是在估计⼀个未知的概率密度函数(PDF),⽽第⼆种情况是你是知道分布的,并想知道哪些参数可以更好的描述数据。
总结:通过seaborn实现直⽅图,可使⽤seaborn.distplot(),seaborn也有单独的kde绘图seaborn.kde()。
在Pandas中的其它⼯具
除了绘图⼯具外,pandas也提供了⼀个⽅便的.value_counts() ⽅法,⽤来计算⼀个⾮空值的直⽅图,并将之转变成⼀个pandas的series 结构,⽰例如下:>>> import pandas as pd
>>> data = np.random.choice(np.arange(10), size=10000,
... p=np.linspace(1, 11, 10) / 60)
>>> s = pd.Series(data)
>>> s.value_counts()
9 1831
8 1624
7 1423
6 1323
5 1089
4 888
3 770
2 535

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。