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

直方图

直方图是一个可以快速展示数据概率分布的工具,直观易于理解,并深受数据爱好者的喜爱。大家平时可能见到最多就是 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] = 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)

之前我们的做法是,在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)

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')

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

该使用哪个方法?

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

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

总结

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

(0)

相关推荐

  • Python绘制频率分布直方图的示例

    项目中在前期经常要看下数据的分布情况,这对于探究数据规律非常有用.概率分布表示样本数据的模样,长的好不好看如果有图像展示出来就非常完美了,使用Python绘制频率分布直方图非常简洁,因为用的频次非常高,这里记录下来.还是Python大法好,代码简洁不拖沓~ 如果数据取值的范围跨度不大,可以使用等宽区间来展示直方图,这也是最常见的一种:如果数据取值范围比较野,也可以自定义区间端点,绘制图像,下面分两种情况展示 1. 区间长度相同绘制直方图 #-*- encoding=utf-8 -*- impor

  • Python基于matplotlib绘制栈式直方图的方法示例

    本文实例讲述了Python基于matplotlib绘制栈式直方图的方法.分享给大家供大家参考,具体如下: 平时我们只对一组数据做直方图统计,这样我们只要直接画直方图就可以了. 但有时候我们同时画多组数据的直方图(比如说我大一到大四跑大学城内环的用时的分布),大一到大四用不同颜色的直方图,显示在一张图上,这样会很直观. #!/usr/bin/env python # -*- coding: utf-8 -*- #http://www.jb51.net/article/100363.htm # nu

  • python OpenCV学习笔记之绘制直方图的方法

    本篇文章主要介绍了python OpenCV学习笔记之绘制直方图的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考.一起跟随小编过来看看吧 官方文档 – https://docs.opencv.org/3.4.0/d1/db7/tutorial_py_histogram_begins.html 直方图会让你对图像的强度分布有一个全面的认识.它是一个在x轴上带有像素值(从0到255,但不总是),在y轴上的图像中对应的像素数量的图. 这只是理解图像的另一种方式.通过观察图像的直方图,你可以直

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

    直方图 直方图是一个可以快速展示数据概率分布的工具,直观易于理解,并深受数据爱好者的喜爱.大家平时可能见到最多就是 matplotlib,seaborn 等高级封装的库包,类似以下这样的绘图. 本篇博主将要总结一下使用Python绘制直方图的所有方法,大致可分为三大类(详细划分是五类,参照文末总结): 纯Python实现直方图,不使用任何第三方库 使用Numpy来创建直方图总结数据 使用matplotlib,pandas,seaborn绘制直方图 下面,我们来逐一介绍每种方法的来龙去脉. 纯Py

  • python中调试或排错的五种方法示例

    前言 本文主要给大家介绍了关于python中调试或排错的五种方法,分享出来供大家参考学习,下面话不多说了,来一起看看详细的的介绍吧 python调试或排错的五种方法 1.print,直接打印,比较简单而且粗暴 在代码中直接输入print+需要输出的结果,根据打印的内容判断即可 2.assert断言,很方便,测试人员常常在写自动化用例的时候用的比较多 如下,直接将预期结果和实际结果做判断 def true_code(): x = 3 y = 2 z = x + y assert(5==z), "z

  • Python实现求解最大公约数的五种方法总结

    目录 方法一:短除法 方法二:欧几里得算法(辗转相除法) 方法三:更相减损术 方法四:穷举法(枚举法) 方法五:Stein算法 求最大公约数是习题中比较常见的类型,下面小编会给大家提供五种比较常见的算法,记得帮忙点个赞哦! 一般来说,最大公约数的求法大概有5种 方法一:短除法 短除法是求最大公因数的一种方法,也可用来求最小公倍数.求几个数最大公因数的方法,开始时用观察比较的方法,即:先把每个数的因数找出来,然后再找出公因数,最后在公因数中找出最大公因数.后来,使用分解质因数法来分别分解两个数的因

  • 利用Python代码实现数据可视化的5种方法详解

    前言 数据科学家并不逊色于艺术家.他们用数据可视化的方式绘画,试图展现数据内隐藏的模式或表达对数据的见解.更有趣的是,一旦接触到任何可视化的内容.数据时,人类会有更强烈的知觉.认知和交流. 数据可视化是数据科学家工作中的重要组成部分.在项目的早期阶段,你通常会进行探索性数据分析(Exploratory Data Analysis,EDA)以获取对数据的一些理解.创建可视化方法确实有助于使事情变得更加清晰易懂,特别是对于大型.高维数据集.在项目结束时,以清晰.简洁和引人注目的方式展现最终结果是非常

  • 利用Python进行数据可视化常见的9种方法!超实用!

    前言 如同艺术家们用绘画让人们更贴切的感知世界,数据可视化也能让人们更直观的传递数据所要表达的信息. 我们今天就分享一下如何用 Python 简单便捷的完成数据可视化. 其实利用 Python 可视化数据并不是很麻烦,因为 Python 中有两个专用于可视化的库 matplotlib 和 seaborn 能让我们很容易的完成任务. Matplotlib:基于Python的绘图库,提供完全的 2D 支持和部分 3D 图像支持.在跨平台和互动式环境中生成高质量数据时,matplotlib 会很有帮助

  • 利用Python将彩色图像转为灰度图像的两种方法

    目录 第一种方法 第二种方法 python 批量将图片转为灰度图 总结 第一种方法 Python的cv2库中自带彩色转灰度的方法,而且非常简单,代码就9行,核心代码就1行. 大题思路就是先读取一张彩色图片,然后在窗口中显示出来,再然后就让cv2处理一下,转换成灰度图像,这时候它是个二维的灰度矩阵,所以,我们想保存得先将它从array转成image,最后在另一个窗口中显示出来,为了避免窗口一闪而过,我们需要加上waitKey(0)这一句. import cv2 from PIL import Im

  • 利用python实现汉字转拼音的2种方法

    前言 在浏览博客时,偶然看到了用python将汉字转为拼音的第三方包,但是在实现的过程中发现一些参数已经更新,现在将两种方法记录一下. xpinyin 在一些博客中看到,如果要转化成带音节的拼音,需要传递参数,'show_tone_marks=True',但我在实际使用时发现,已经没有这个参数了,变成了tone_marks,其它的参数和使用方法,一看就明白了,写的很清楚. 看下源码: class Pinyin(object): """translate chinese han

  • 教你利用Python+Turtle绘制简易版爱心表白

    一.效果 快放10倍 总共分为三部分,左上角的正文,下方的心形和右下角的署名 特别需要注意的一点是这种东西不但要装Python,还与分辨率有关(换个屏幕可能效果雪崩,因为用的是绝对坐标),因此并不建议实际拿去弄(哪怕能解决上述两个问题) 二.正文部分 效果: 本质是每写一行话,然后将坐标下移换行,再写一行,以此类推 # content就是该行的内容了,想些啥写啥吧 def drawLine(content, x, y, sleep=3): goto(x, y) write(content, fo

  • 教你利用Python破解ZIP或RAR文件密码

    一.破解原理 其实原理很简单,一句话概括就是「大力出奇迹」,Python 有两个压缩文件库:zipfile 和 rarfile,这两个库提供的解压缩方法 extractall()可以指定密码,这样的话首先生成一个密码字典(手动或用程序),然后依次尝试其中的密码,如果能够正常解压缩表示密码正确. 二.实验环境 本文采取的虚拟环境为 Pipenv. 库 zipfile:Python 标准库,使用时直接导入即可 rarfile:Python 第三方库 利用 Pipenv 安装 rarfile pipe

  • 教你利用python实现企业微信发送消息

    一.需要的参数 1.通讯用户:touser 或 通讯组:toparty 2.企业ID:corpid 3.应用ID/密钥:agentId,secret 二.获取通讯用户/组 通讯录 用户的账号或创建组的部门ID 三.获取企业ID 我的企业最下方 四.获取应用ID/密钥 企业微信管理员登录企业微信, 应用管理创建应用 可见范围:发给谁 五.脚本代码 #! /usr/bin/env python # -*- coding: UTF-8 -*- import requests, sys class Se

随机推荐