社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

Python绘制histogram直方图(附代码)

中国统计网 • 5 年前 • 636 次点击  


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


01 纯Python实现histogram


当准备用纯Python来绘制直方图的时候,最简单的想法就是将每个值出现的次数以报告形式展示。这种情况下,使用 字典 来完成这个任务是非常合适的,我们看看下面代码是如何实现的。

>>> a = (0111237723)

>>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
{0113213172231}

我们看到count_elements() 返回了一个字典,字典里出现的键为目标列表里面的所有唯一数值,而值为所有数值出现的频率次数。hist[i] = hist.get(i, 0) + 1 实现了每个数值次数的累积,每次加一。

实际上,这个功能可以用一个Python的标准库 collection.Counter 类来完成,它兼容Pyhont 字典并覆盖了字典的 .update() 方法。


>>> from collections import Counter

>>> recounted = Counter(a)
>>> recounted
Counter({0113312 172231})

可以看到这个方法和前面我们自己实现的方法结果是一样的,我们也可以通过 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 = [13468910]
>># `vals` 里面的数字将会出现5到15次
>>> freq = (random.randint(515) 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方法实现。


02 使用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.40618.08716.00416.221,  7.358])


由于这是一个连续型的分布,对于每个单独的浮点值(即所有的无数个小数位置)并不能做很好的标签(因为点实在太多了)。但是,你可以将数据做 分箱 处理,然后统计每个箱内观察值的数量,这就是真正的直方图所要做的工作。


下面我们看看是如何用Numpy来实现直方图频数统计的。


>>> hist, bin_edges = np.histogram(d)

>>> hist
array([ 1,  0,  3,  4,  41013,  9,  2,  4])

>>> bin_edges
array([ 3.217,  5.199,  7.181,  9.16311.14513.12715.10917.091,
       19.07321.05523.037])

这个结果可能不是很直观。来说一下,np.histogram() 默认地使用10个相同大小的区间(箱),然后返回一个元组(频数,分箱的边界),如上所示。要注意的是:这个边界的数量是要比分箱数多一个的,可以简单通过下面代码证实。

>>> hist.sizebin_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.211.513.816.118.420.723. ])

解释一下:首先获取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()]))
{0113213172231}


总结:通过Numpy实现直方图,可直接使用np.histogram()或者np.bincount()。


02 使用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(2345r'$\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 = 100010
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()


03 在Pandas中的其它工具


除了绘图工具外,pandas也提供了一个方便的.value_counts() 方法,用来计算一个非空值的直方图,并将之转变成一个pandas的series结构,示例如下:


>>> import pandas as pd

>>> data = np.random.choice(np.arange(10), size=10000,
...                         p=np.linspace(11110) / 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(
...     [11358101215181819202530405152])
>>> bins = (010131821, 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()

End.

来源:Python数据科学

作者:Drdata


今天看啥 - 高品质阅读平台
本文地址:http://www.jintiankansha.me/t/h0Ut3vYPX3
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/21112
 
636 次点击