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 这种无效数据),所以你在结果中能看到 c0
,c2
两个的有效数是 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 画图还可以有很多其他花样, 你可以在这里看到更加细节的官方文档。 每种画图功能里面,也还有更多参数可以调整。记得不懂就要多在网上搜搜,多看官方文档。