Pandas统计展示

​ 本文主要写Pandas在统计展示方面的知识。

基础统计方法

​ 在Pandas上做数据统计,要比在Numpy上做舒服很多,因为在数据展示上,有很多额外的信息辅助你来消化这些信息。而且你还能比较方便地绘制成图。

​ 在本节内容中我们会对比Numpy中的方法和Pandas的不同,来解释为什么人们在做数据分析的时候喜欢用Pandas。

数据

​ 在分析数据或者统计数据的时候,首先得有数据,我简单创建了一份数据,后续的工作将会依赖于这份数据,所以请你帮我点击一下下面的 Run 键,初始化一下这份数据。

import pandas as pd
import numpy as np

data = np.array([
    [1.39, 1.77, None],
    [0.34, 1.91, -0.05],
    [0.34, 1.47, 1.22],
    [None, 0.27, -0.61]
])
df = pd.DataFrame(data, index=["r0", "r1", "r2", "r3"], columns=["c0", "c1", "c2"])
df

​  运行结果:

      c0    c1    c2
r0  1.39  1.77  None
r1  0.34  1.91 -0.05
r2  0.34  1.47  1.22
r3  None  0.27 -0.61

​ 你在日常生活中,也经常是以这种 2 维表格型数据为主,而且因为各种不可知原因,你的数据可能存在缺失状况。比如有人没交作业,有数据还没被采集到等等。 如果你使用 Excel 收集的数据(用 Pandas 读 Excel),那这种情况可能更加多。

快速总结

​ 通常,如果我们不知道这份数据包含了什么,想快速了解一下这份数据的基础信息,我们可以直接先上一个 describe(),让 Pandas 自动帮我们描述一下这份数据的基础信息。

df.describe()

​ 运行结果:

          c0    c1    c2
count   3.00  4.00  3.00
unique  2.00  4.00  3.00
top     0.34  1.77 -0.05
freq    2.00  1.00  1.00

​ 这里,会显示出来 count 计数(剔除掉 None 或者 NAN 这种无效数据),所以你在结果中能看到 c0c2 两个的有效数是 3 个,而 c1 有效数有 4 个。

unique 表示的是每个 column 中有多少独特的数据。这个在初步感知数据丰富度上会有一定的作用。

top 表示出现最多的数据是哪一个,这组数据在 c0 column 处,我们能观察到 0.34 出现了两次,所以它选的 top 是 0.34

freq 是继续了 top,表述的是这个出现频率最多的数据,出现的次数有多少次。

​ 上面这份数据还不是纯数据,如果是存数值型的数据,我们跑 describe() 还能看到统计学的信息。

df1 = pd.DataFrame(np.random.random((4,3)),index=["a","b","c","d"],columns=["c0", "c1", "c2"])
print(df1)
print("\ndescribe:\n", df1.describe())

​ 运行结果:

         c0        c1        c2
a  0.053206  0.686505  0.439459
b  0.083406  0.096903  0.212163
c  0.369903  0.877894  0.918427
d  0.169573  0.563210  0.900700

describe:
              c0        c1        c2
count  4.000000  4.000000  4.000000
mean   0.169022  0.556128  0.617687
std    0.142708  0.332400  0.349645
min    0.053206  0.096903  0.212163
25%    0.075856  0.446634  0.382635
50%    0.126490  0.624858  0.670080
75%    0.219656  0.734352  0.905132
max    0.369903  0.877894  0.918427

​ 平均数(mean),均方差(std),最小值(min),统计学的 25 分位,50 分位,75 分位各是多少,最大值(max)是多少。

日常一般用法

  • 均值中位数df.mean();df.median()

​ 像上面,我们已经可以从 describe() 功能中略知一二了,但是你肯定也不满足于这些简单的描述信息。那么我们看看还有哪些简单实用的统计学功能。 比较常用的,我们通常会想知道一组数据的均值,用 mean() 就好了。

df.mean()

​ 这样可以直接输出每一个 columns 的均值,因为这是在对 df 的第0个维度在做求均值。也可以这么写。

df.mean(axis=0)

​  运行结果:

c0    0.690000
c1    1.355000
c2    0.186667
dtype: float64

​ 当然,如果你不想对第 0 个维度,而是想对第 1 个维度求均值呢(后面的功能中 axis 的用法都基本相似)。我们只需要把 axis=0 换成 axis=1

df.mean(axis=1)

​ 运行结果:

r0    1.580000
r1    0.733333
r2    1.010000
r3   -0.170000
dtype: float64

​ 还有一个比较有用的参数 skipna,这个是用来处理数据中有 None 或者是 NaN 时用的。我们需不需要排除掉有 None 或者 NaN 的数据。 如果需要 skip 掉这些,我们就还是会计算所有行列的数值,只是在计数的时候,扣掉这些 None 和 NaN。而当 skipna=False 的时候, Pandas 只要遇到了 None 或者 NaN,就不计算这列、行的数据了。所以下面你会看到,它只返回了一个 column 的结果。

df.mean(axis=0,skipna=False)
#c1    1.355
#dtype: float64

​ 你看,对比 Numpy 的用法,你会发现, Pandas 在展示信息上还是挺对用户考虑的,它把行名等都展示出来,让人不犯迷糊。

​ 有了上面的 mean() 的用法做铺垫,理解后面的用法也方便很多。比如在计算人民收入的时候, 我们常用中值来代替均值,原因很简单,极高收入群体总是拉高了我们的平均收入, 用中位数反倒能反映出群众的真实收入。 查中值的方式也很简单,就是 median()

# 最后一个为高收入人
s = pd.Series([1000, 2000, 4000, 100000])
print("mean():", s.mean())   # 拉高平均收入,拉高仇恨
#mean(): 26750.0
print("median():", s.median())  # 比较合理
#median(): 3000.0
  • 累加累乘df.sum();df.prod()

​ 有了上面的mean()median()的用法做铺垫,理解后面的用法也方便很多。比如要对数据做累加和累乘的运算,我们使用方式和mean()就没啥差别。

df = pd.DataFrame(np.arange(12).reshape((4,3)), columns=["c0","c1","c2"])
df

​ 运行结果:

   c0  c1  c2
0   0   1   2
1   3   4   5
2   6   7   8
3   9  10  11
print("sum():\n", df.sum())
print("\nsum(axis=0):\n", df.sum(axis=0))
print("\nsum(axis=1):\n", df.sum(axis=1))

​ 运行结果:

sum():
 c0    18
c1    22
c2    26
dtype: int64

sum(axis=0):
 c0    18
c1    22
c2    26
dtype: int64

sum(axis=1):
 0     3
1    12
2    21
3    30
dtype: int64
print("prod():\n", df.prod())
print("\nprod(axis=0):\n", df.prod(axis=0))
print("\nprod(axis=1):\n", df.prod(axis=1))

​ 运行结果:

prod():
 c0      0
c1    280
c2    880
dtype: int32

prod(axis=0):
 c0      0
c1    280
c2    880
dtype: int32

prod(axis=1):
 0      0
1     60
2    336
3    990
dtype: int32
  • 最大最小df.max(); df.min()

​ 同理,理解了上面的用法,查找最大最小也不是问题。

print("max():\n", df.max())
print("\nmin():\n", df.min())

​ 运行结果:

max():
 c0     9
c1    10
c2    11
dtype: int32

min():
 c0    0
c1    1
c2    2
dtype: int32

​ 不过你注意到没,这种 max()min() 都是对某一维度进行操作的,肯定有时候,你想要找到那个全局最大最小的数,这怎么找? 哈哈,那你就做两次操作吧。或者你可以先把它转成 numpy,然后展平了求全局最大最小。

print(df.max().max())
print(df.values.ravel().max())  # 用 Numpy 的方式运算
#11
#11

​ 如果想计算 mean 或者 median 这种,你想想然后再试试,用哪种方式比较合适呢?

  • 处理空值 df.isnull(); df.notnull(); df.dropna(); df.fillna()

​ 总有 None 或者 NaN 值有时候挺烦人的,因为在做机器学习或者是统计数据的时候,你也不能放它在那不管。比如在机器学习中,如果有空值,你要么就选择放弃这条数据, 要么就要对它进行科学的填充,有人用均值有人用中值等。所以上面学到的技巧都能在这里用上。

​ 第一,你可能想要先看看你的数据中有没有空值。用下面的 isnull() 或者 notnull() 就能找到。

df = pd.DataFrame([[1, 2, 3, 0],
                   [3, 4, None, 1],
                   [None, None, None, None],
                   [None, 3, None, 4]],
                  columns=list("ABCD"))
print(df)
print("\nisnull():\n", df.isnull())  # True 就是空
print("\nnotnull()\n", df.notnull())  # False 为空

​ 运行结果:

     A    B    C    D
0  1.0  2.0  3.0  0.0
1  3.0  4.0  NaN  1.0
2  NaN  NaN  NaN  NaN
3  NaN  3.0  NaN  4.0

isnull():
        A      B      C      D
0  False  False  False  False
1  False  False   True  False
2   True   True   True   True
3   True  False   True  False

notnull()
        A      B      C      D
0   True   True   True   True
1   True   True  False   True
2  False  False  False  False
3  False   True  False   True

​ 发现这里有空值,下面你就可以对这些 None, Null, NaN 做处理了。要么你就放弃这些有空值的数据,用 dropna()

print("默认:\n", df.dropna())  # 默认按 axis=0
print("\naxis=1:\n", df.dropna(axis=1))  # 可以换一个 axis drop

​ 运行结果:

默认:
      A    B    C    D
0  1.0  2.0  3.0  0.0

axis=1:
 Empty DataFrame
Columns: []
Index: [0, 1, 2, 3]

​ 当然,你觉得数据只要有值你就想留下来,只去除掉那些全为空的数据,那么你还能在筛选的时候加一个 how="all" 参数。

df1 = pd.DataFrame([[None, None, None], [1,None,3]])
print(df1.dropna(how="all"))  # how 默认为 "any" 
#     0     1    2
#1  1.0  None  3.0

​ 刚也说了,除了 drop 掉有 None 的,还可以对这些空值进行填充,填充的值也可以自行选定。

df.fillna(111)  # 填充 111

​ 对不同特征做差异化的填充数值。

values = {"A": 0, "B": 1, "C": 2, "D": 3}
df.fillna(value=values)

​ 甚至,如果你有一个每一位上的默认值,你都可以用一个全新的 df 来做空位的填充。

df2 = pd.DataFrame(np.arange(16).reshape((4,4)), columns=list("ABCD"))
print("df2:\n", df2)
print("\nfillna(df2):\n", df.fillna(df2))

​ 运行结果:

df2:
     A   B   C   D
0   0   1   2   3
1   4   5   6   7
2   8   9  10  11
3  12  13  14  15

fillna(df2):
       A    B     C     D
0   1.0  2.0   3.0   0.0
1   3.0  4.0   6.0   1.0
2   8.0  9.0  10.0  11.0
3  12.0  3.0  14.0   4.0
  • 获取索引 df.idxmin(); df.idxmax()

​ 一般来说,当你想用 np.argmax() 或者 np.argmin() 的时候,你可以在 pandas 用 idxmax()idxmin() 来替换。原理都一样, 就是找到那个最大最小值的索引。这个的好处是,你只关注索引而不用关注值,你可以对这个索引的值做你想要的后续处理。

同上面一样,你还能用上面学到的 skipna 来对空值做控制。

df = pd.DataFrame([[1, 2, 3, 0],
                   [3, 4, None, 1],
                   [3, 5, 2, 1],
                   [3, 2, 2, 3]],
                  columns=list("ABCD"))
print(df)
print("\nidxmax():\n", df.idxmax())
print("\nidxmax(skipna=False):\n", df.idxmax(skipna=False))
print("\nidxmin():\n", df.idxmin())

​ 运行结果:

   A  B    C  D
0  1  2  3.0  0
1  3  4  NaN  1
2  3  5  2.0  1
3  3  2  2.0  3

idxmax():
 A    1
B    2
C    0
D    3
dtype: int64

idxmax(skipna=False):
 A    1.0
B    2.0
C    NaN
D    3.0
dtype: float64

idxmin():
 A    0
B    0
C    2
D    0
dtype: int64

总结

​ 在机器学习或者统计学中,只要你是和数据打交道,就少不了先观察和了解数据。用 Pandas 的这些功能,可以帮你快速了解数据的全貌, 也可以对其中的数据缺失做一些处理。

绘制图表

​ 数据是服务于人的决策的,我们有一大堆数据,如果人没有真正意义上理解这些数据背后的含义,那即使数据量再大,它也是无意义的。所以我们今天来探讨一种让人与数据之间构建信息传递桥梁的方法-数据可视化技术。

​ 在 Pandas 中, 就已经可以实现多种多样的数据可视化方案了。

​ 我们来看看你拥有的数据可以被 Pandas 表达成什么样。

散点图Scatter

​ 散点图实际在很多生活场景上都有运用的。比如你要描绘数据 sample 之间与拟合曲线之间呈现的关系:

​ 又或者在演示算法是如何进化 ,如何运动的。

​ 这些都是我之前做过的散点图案例,所以适用场景还是非常多的。在 Pandas 中,我们有非常方便的办法来直接对 DataFrame 做散点图。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt #一定要加上
n = 1024 			# data size
df = pd.DataFrame({
  "x": np.random.normal(0,1,n),
  "y": np.random.normal(0,1,n),
})
color = np.arctan2(df["y"], df["x"])
df.plot.scatter(x="x", y="y", c=color, s=60, alpha=.5, cmap="rainbow")
plt.show()

​ 点击运行之后,你会发现可以做出来一幅非常好看的图。当然这里的参数你可以随意搭配,比如试试不写 c=color 或者去掉 cmap="rainbow", 看看会有何影响。

​ 我稍微解释一下几个你可能在乎的参数:

  • c: 对于这组数据中每个(x,y)数据点的颜色值
  • s: 画点的大小(size)
  • alpha:不透明度
  • cmap:colormap,你可以在这里找到非常丰富的案例

折线图 plot

​ 同样在这个拟合关系的图中, 我们其实还绘制了一张折线图。可见,折线图也是非常有用的。

​ 同样,还有很多机器学习的loss变化曲线案例都是用折线图绘制出来的。而在Pandas中,折线图的绘制方法很简单。

n = 20    # data size
x = np.linspace(-1, 1, n)
y = x * 2 + 0.4 + np.random.normal(0, 0.3, n)
df = pd.DataFrame({
    "x": x,
    "y": y, 
})
df.plot(x="x", y="y", alpha=.5, c="r")

![](http://cdn.leafii.top/img/Screen Shot 2022-06-08 at 21.07.19.png)

​ 我用最朴素的方法,绘制了一条歪歪扭扭的折线图,来体现它真的很折。你可能很感兴趣,为什么这里的 c 参数传入的数值和上面散点图的不一样? 原来在折线图中,线的颜色最好是一样的,不然当线多了以后,你会发现不同颜色就看不出到底是那条线了。

​ 碰到多条线的时候,怎么处理:

n = 20 # data size
x = np.linspace(-1, 1, n)
y1 = x * -1 - 0.1 + np.random.normal(0, 0.3, n)
y2 = x * 2 + 0.4 + np.random.normal(0, 0.3, n)
df = pd.DataFrame({
  "x":x,
  "y1": y1,
  "y2": y2,
})
df.plot(x="x", y=["y1", "y2"], alpha=.5)
plt.show()

​ 只要我给多个 y,它就能给出多条线的位置,当然还帮你注明哪个颜色是哪条线。

条形图 bar

​ 做两家公司收入对比,或者是年度值变化的时候,我们也很喜欢用条形图。直接点,我们看看 Pandas 的条形图怎么画。 假设有 abd 四家公司,这 5 年的营收对比可以这么画。

df = pd.DataFrame(np.random.rand(5, 3), columns=["a", "b", "c"])
df.plot.bar()
plt.show()

​ 如果把他们放在一起来看占比多少时,我们还能这么干:

df.plot.bar(stacked=True)
plt.show()

​ 横着不好看,想画一个竖着的图,Pandas 也能轻松做到。你只需要把 bar() 换成 barh() 就好。多出来的这个 h 就是 horizontal 的意思。

df.plot.barh()
plt.show()

分布图 histograms

​ 分布图在机器学习和统计学中非常重要,我经常画分布图,比如要画神经网络的参数分布可视化。又或者是GAN生成对抗网络中的数据分布。

​ 我们用plot.hist()就能画出来了,这里的hist是histogram,也就是分布的意思。

df = pd.DataFrame({"a": np.random.randn(1000)})
df.plot.hist()
plt.show()

​ 当然还会有多个分布重合在一起,你想对比这看看这些分布有无差别的时候,重合度怎么样的时候。

df = pd.DataFrame(
{
  "a": np.random.randn(1000) + 1,
  "b": np.random.randn(1000),
  "c": np.random.randn(1000) - 4,
}
)
df.plot.hist(alpha=0.5, bins=30)
plt.show()

​ 为了使你能轻松辨析出 abc 这几个分布的不同,我把 alpha 不透明度调整了一下,让你能看清楚重叠部分。而且 bins 柱状体的数量也调多了。

饼图 Pie

​ 当你想给 Excel 批量话饼图的时候, 你就能结合读取 Excel 的教学,和这一节一起用。

df = pd.DataFrame(
{"boss": np.random.rand(4)},
index=["meeting", "supervise", "teaching", "team building"],
)
df.plot.pie(y="boss", figsize=(7,7))
plt.show()

​ 如果你有多张大饼,想要对比?当然也没问题。可以多加一个 subpots 来分开画饼。legend 是用来确定要不要输出图例的,我这里嫌弃图例占地方, 就设置 legend=False

df = pd.DataFrame(
    {
        "bigBoss": np.random.rand(4),
        "smallBoss": np.random.rand(4),
    },
    index=["meeting", "supervise", "teaching", "team building"], 
)
df.plot.pie(subplots=True, figsize=(9,9), legend=False)
plt.show()

面积图 area

​ 面积图偶尔你还是会看见的,比如在我的 进化算法教学中, 就使用了面积图来观看各个种群的占比随时间的变化情况。

df = pd.DataFrame(
    np.random.rand(10, 4), 
    columns=["a", "b", "c", "d"]
)
df.plot.area()

​ 如果你不想上下堆砌在一起观看,而是有统一的一个起点,那可以用这个参数 stacked=False

df.plot.area(stacked=False)

总结

​ 好了,这节内容也还挺多的,我就先讲到这里。用 Pandas 画图还可以有很多其他花样, 你可以在这里看到更加细节的官方文档。 每种画图功能里面,也还有更多参数可以调整。记得不懂就要多在网上搜搜,多看官方文档。