教你利用Python玩转histogram直方图的五种方法


Posted in Python onJuly 30, 2018

直方图

直方图是一个可以快速展示数据概率分布的工具,直观易于理解,并深受数据爱好者的喜爱。大家平时可能见到最多就是 matplotlib,seaborn 等高级封装的库包,类似以下这样的绘图。

教你利用Python玩转histogram直方图的五种方法

本篇博主将要总结一下使用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] = hist.get(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] = hist.get(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):
... data.extend([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), bcounts[bcounts.nonzero()]))
{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.grid(axis='y', alpha=0.75)
plt.xlabel('Value')
plt.ylabel('Frequency')
plt.title('My Very Own Histogram')
plt.text(23, 45, r'$\mu=15, b=3$')
maxfreq = n.max()
# 设置y轴的上限
plt.ylim(ymax=np.ceil(maxfreq / 10) * 10 if maxfreq % 10 else maxfreq + 10)

教你利用Python玩转histogram直方图的五种方法

之前我们的做法是,在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')
plt.grid(axis='y', alpha=0.75)

教你利用Python玩转histogram直方图的五种方法

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和DataFrame数据结构都适用。但是首先,我们先生成两个不同的数据样本作为比较(两个正太分布的样本):

>>> # 两个正太分布的样本
>>> means = 10, 20
>>> stdevs = 4, 2
>>> dist = pd.DataFrame(
...  np.random.normal(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.grid(axis='y')
ax.set_facecolor('#d8dcd6')

教你利用Python玩转histogram直方图的五种方法

总结:通过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)

教你利用Python玩转histogram直方图的五种方法

distplot方法默认的会绘制kde,并且该方法提供了 fit 参数,可以根据数据的实际情况自行选择一个特殊的分布来对应。

sns.distplot(d, fit=stats.laplace, kde=False)

教你利用Python玩转histogram直方图的五种方法

注意这两个图微小的区别。第一种情况你是在估计一个未知的概率密度函数(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
1  347
0  170
dtype: int64

>>> s.value_counts(normalize=True).head()
9 0.1831
8 0.1624
7 0.1423
6 0.1323
5 0.1089
dtype: float64

此外,pandas.cut() 也同样是一个方便的方法,用来将数据进行强制的分箱。比如说,我们有一些人的年龄数据,并想把这些数据按年龄段进行分类,示例如下:

>>> ages = pd.Series(
...  [1, 1, 3, 5, 8, 10, 12, 15, 18, 18, 19, 20, 25, 30, 40, 51, 52])
>>> bins = (0, 10, 13, 18, 21, np.inf) # 边界
>>> labels = ('child', 'preteen', 'teen', 'military_age', 'adult')
>>> groups = pd.cut(ages, bins=bins, labels=labels)

>>> groups.value_counts()
child   6
adult   5
teen   3
military_age 2
preteen   1
dtype: int64

>>> pd.concat((ages, groups), axis=1).rename(columns={0: 'age', 1: 'group'})
 age   group
0  1   child
1  1   child
2  3   child
3  5   child
4  8   child
5 10   child
6 12  preteen
7 15   teen
8 18   teen
9 18   teen
10 19 military_age
11 20 military_age
12 25   adult
13 30   adult
14 40   adult
15 51   adult
16 52   adult

除了使用方便外,更加好的是这些操作最后都会使用 Cython 代码来完成,在运行速度的效果上也是非常快的。

总结:其它实现直方图的方法,可使用.value_counts()pandas.cut()

该使用哪个方法?

至此,我们了解了很多种方法来实现一个直方图。但是它们各自有什么有缺点呢?该如何对它们进行选择呢?当然,一个方法解决所有问题是不存在的,我们也需要根据实际情况而考虑如何选择,下面是对一些情况下使用方法的一个推荐,仅供参考。

教你利用Python玩转histogram直方图的五种方法

参考:https://realpython.com/python...

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Python 相关文章推荐
python抓取网页图片示例(python爬虫)
Apr 27 Python
Python常用正则表达式符号浅析
Aug 13 Python
使用Python对MySQL数据操作
Apr 06 Python
python 用opencv调用训练好的模型进行识别的方法
Dec 07 Python
使用Python给头像戴上圣诞帽的图像操作过程解析
Sep 20 Python
python 协程中的迭代器,生成器原理及应用实例详解
Oct 28 Python
python 协程 gevent原理与用法分析
Nov 22 Python
Pycharm如何导入python文件及解决报错问题
May 10 Python
基于python SMTP实现自动发送邮件教程解析
Jun 02 Python
如何利用python web框架做文件流下载的实现示例
Jun 02 Python
社区版pycharm创建django项目的方法(pycharm的newproject左侧没有项目选项)
Sep 23 Python
刚学完怎么用Python实现定时任务,转头就跑去撩妹!
Jun 05 Python
对TensorFlow中的variables_to_restore函数详解
Jul 30 #Python
Python实现模拟浏览器请求及会话保持操作示例
Jul 30 #Python
tensorflow 打印内存中的变量方法
Jul 30 #Python
Python实现的多叉树寻找最短路径算法示例
Jul 30 #Python
tensorflow: variable的值与variable.read_value()的值区别详解
Jul 30 #Python
Tensorflow 实现修改张量特定元素的值方法
Jul 30 #Python
python用BeautifulSoup库简单爬虫实例分析
Jul 30 #Python
You might like
PHP 线程安全与非线程安全版本的区别深入解析
2013/08/06 PHP
js 获取浏览器高度和宽度值(多浏览器)
2009/09/02 Javascript
JavaScript 学习笔记一些小技巧
2010/03/28 Javascript
niceTitle 基于jquery的超链接提示插件
2010/05/31 Javascript
Javascript生成json的函数代码(可以用php的json_decode解码)
2012/06/11 Javascript
javascript 回到顶部效果的实现代码
2014/02/17 Javascript
分享一则javascript 调试技巧
2015/01/02 Javascript
javascript实现通过表格绘制颜色填充矩形的方法
2015/04/21 Javascript
手机端转盘抽奖代码分享
2015/09/10 Javascript
jQuery页面加载初始化的3种方法(推荐)
2016/06/02 Javascript
jQuery常用样式操作实例分析(获取、设置、追加、删除、判断等)
2016/09/08 Javascript
JS中判断null的方法分析
2016/11/21 Javascript
无法获取隐藏元素宽度和高度的解决方案
2017/03/07 Javascript
node.js+captchapng+jsonwebtoken实现登录验证示例
2017/08/17 Javascript
vue-for循环嵌套操作示例
2019/01/28 Javascript
JS实现的检验身份证格式并输出出生日期,年龄,性别,出生地示例
2019/05/17 Javascript
vue日历/日程提醒/html5本地缓存功能
2019/09/02 Javascript
基于vue.js实现购物车
2020/01/15 Javascript
js找出5个数中最大的一个数和倒数第二大的数实现方法示例小结
2020/03/04 Javascript
JS严格模式原理与用法实例分析
2020/04/27 Javascript
python 转换 Javascript %u 字符串为python unicode的代码
2016/09/06 Python
PIL对上传到Django的图片进行处理并保存的实例
2019/08/07 Python
Python 、Pycharm、Anaconda三者的区别与联系、安装过程及注意事项
2019/10/11 Python
python 使用openpyxl读取excel数据
2021/02/18 Python
美国市场上最实惠的送餐服务:Dinnerly
2018/03/18 全球购物
法国一家芭蕾舞鞋公司:Repetto
2018/11/12 全球购物
三星法国官方网站:Samsung法国
2019/10/31 全球购物
DIY蛋糕店的创业计划书范文
2013/12/26 职场文书
小学生新学期寄语
2014/01/19 职场文书
我的长生果教学反思
2014/04/28 职场文书
国际贸易系求职信
2014/08/09 职场文书
2014年乡镇民政工作总结
2014/12/02 职场文书
2015年安置帮教工作总结
2015/05/22 职场文书
2015年惩防体系建设工作总结
2015/05/22 职场文书
分析mysql中一条SQL查询语句是如何执行的
2021/06/21 MySQL
 分享一个Python 遇到数据库超好用的模块
2022/04/06 Python