这将是Pandas入门篇的最终篇了,人看麻了。

一、带有重复标签的轴索引

直到目前为止,所有范例都有着唯一的轴标签(索引值)。虽然许多pandas函数(如reindex)都要求标签唯一,但这并不是强制性的。来看看下面这个简单的带有重复索引值的Series:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import pandas as pd
obj = pd.Series(range(5), index=["a", "a", "b", "b", "c"])
print("-----obj-----")
print(obj)

# 索引的is_unique属性可以告诉你它的值是否是唯一的
print("-----obj.index.is_unique-----")
print(obj.index.is_unique)

# 结果
-----obj-----
a 0
a 1
b 2
b 3
c 4
dtype: int64
-----print(obj.index.is_unique)-----
False

对于带有重复值的索引,数据选取的行为将会有些不同。如果某个索引对应多个值,则返回一个Series;而对应单个值的,则返回一个标量值:

1
2
3
4
5
6
7
8
9
10
11
···
print("-----obj['a']-----")
print(obj['a'])

# 结果
-----obj['a']-----
a 0
a 1
dtype: int64
-----obj['c']-----
4

这样会使代码变复杂,因为索引的输出类型会根据标签是否有重复发生变化。

对DataFrame的行进行索引时也是如此:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
df = pd.DataFrame(np.random.randn(4,3), index=["a", "a", "b", "b"])
print("------df-----")
print(df)
print("-----df.loc['b']-----")
print(df.loc['b'])

# 结果
------df-----
0 1 2
a -1.546672 -0.810346 -0.045471
a -1.340069 -0.827010 -1.070048
b 1.974275 0.189458 -0.988055
b 0.895172 -1.736643 1.619483
-----df.loc['b']-----
0 1 2
b 1.974275 0.189458 -0.988055
b 0.895172 -1.736643 1.619483

二、汇总和计算描述统计

2.1 简约型

pandas对象拥有一组常用的数学和统计方法。它们大部分都属于约简和汇总统计,用于从Series中提取单个值(如sum或mean)或从DataFrame的行或列中提取一个Series。跟对应的NumPy数组方法相比,它们都是基于没有缺失数据的假设而构建的。看一个简单的DataFrame:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import pandas as pd
import numpy as np

df = pd.DataFrame([[1.4, np.nan], [7.1, -4.5],
[np.nan, np.nan], [0.75, -1.3]],
index=['a', 'b', 'c', 'd'],
columns=['one', 'two'])
print("-----df-----")
print(df)

# 结果
-----df-----
one two
a 1.40 NaN
b 7.10 -4.5
c NaN NaN
d 0.75 -1.3

调用DataFrame的sum方法将会返回一个含有列的和的Series:

1
2
3
4
5
6
7
print("-----df.sum()-----")
print(df.sum())
# 结果
-----df.sum()-----
one 9.25
two -5.80
dtype: float64

传入axis=’columns’或axis=1将会按行进行求和运算:

1
2
3
4
5
6
7
···
-----df.sum(axis=1)-----
a 1.40
b 2.60
c 0.00
d -0.55
dtype: float64

注意,在这里和书上讲的不同这里直接将NaN排除了,整个切片都是NaN时值为0.00

通过skipna选项可以禁用该功能:

1
2
3
4
5
6
7
8
9
···
print(df.mean(axis=1, skipna=False))

# 结果
a NaN
b 1.300
c NaN
d -0.275
dtype: float64

下表列出了一些简约方法的常用选项:

选项 说明
axis
skipna 排除缺失值,默认为True
level 如果轴时层次化索引的(即MultiIndex),则根据level分组轴

有些方法(如idxmin和idxmax)返回的是间接统计(比如达到最小值或最大值的索引):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
print("-----df.idmix()-----")
print(df.idxmin())
print("-----df.idmax()-----")
print(df.idxmax())

# 结果,两列同时索引返回Series
-----df.idmix()-----
one d
two b
dtype: object
-----df.idmax()-----
one b
two d
dtype: object

2.2 累计型

另一些方法则是累计型的:

1
2
3
4
5
6
7
8
9
10
11
···
print("-----df.cumsum()-----")
print(df.cumsum())

# 结果
-----df.cumsum()-----
one two
a 1.40 NaN
b 8.50 -4.5
c NaN NaN
d 9.25 -5.8

2.3 非累计非简约型

还有一种方法,它既不是约简型也不是累计型。describe就是一个例子,它用于一次性产生多个汇总统计:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
···
print("-----df.describe()-----")
print(df.describe())

# 结果
-----df.describe()-----
one two
count 3.000000 2.000000
mean 3.083333 -2.900000
std 3.493685 2.262742
min 0.750000 -4.500000
25% 1.075000 -3.700000
50% 1.400000 -2.900000
75% 4.250000 -2.100000
max 7.100000 -1.300000

对于非数值型数据,describe会产生另外一种汇总统计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
···
obj = pd.Series(["a", "a", "b", "c"] * 4)
print("-----obj-----")
print(obj)
print("-----obj.describe()-----")
print(obj.describe())

# 结果
-----obj-----
0 a
1 a
2 b
3 c
4 a
5 a
6 b
7 c
8 a
9 a
10 b
11 c
12 a
13 a
14 b
15 c
dtype: object
-----obj.describe()-----
count 16
unique 3
top a
freq 8
dtype: object

下图列出了所有与描述统计相关的方法

三、唯一值、值计数以及成员资格

还有一类方法可以从一维Series的值中抽取信息。看下面的例子:

1
2
import pandas as pd
obj = pd.Series(['c', 'a', 'd', 'a', 'a', 'b', 'b', 'c', 'c'])

第一个函数是unique,它可以得到Series中的唯一值数组:

1
2
3
4
5
6
7
8
···
print("-----obj.unique()-----")
uniques = obj.unique()
print(uniques)

# 结果
-----obj.unique()-----
['c' 'a' 'd' 'b']

返回的唯一值是未排序的,如果需要的话,可以对结果再次进行排序(uniques.sort())。相似的,value_counts用于计算一个Series中各值出现的频率:

1
2
3
4
5
6
7
8
9
10
11
···
print("-----obj.value_counts()-----")
print(obj.value_counts())

# 结果
-----obj.value_counts()-----
c 3
a 3
b 2
d 1
dtype: int64

isin用于判断矢量化集合的成员资格,可用于过滤Series中或DataFrame列中数据的子集:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
···
print("-----obj-----")
print(obj)
print("-----obj.isin(['b', 'c'])-----")
print(obj.isin(["b", "c"]))
mask = obj.isin(["b", "c"])
print("-----obj[mask]-----")
print(obj[mask])

# 结果
-----obj-----
0 c
1 a
2 d
3 a
4 a
5 b
6 b
7 c
8 c
dtype: object
-----obj.isin(['b', 'c'])-----
0 True
1 False
2 False
3 False
4 False
5 True
6 True
7 True
8 True
dtype: bool
-----obj[mask]-----
0 c
5 b
6 b
7 c
8 c
dtype: object

与isin类似的是Index.get_indexer方法,它可以给你一个索引数组,从可能包含重复值的数组到另一个不同值的数组:

1
2
3
4
5
6
7
···
to_match = pd.Series(["c", "a", "b", "b", "c", "a"])
unique_vals = pd.Series(["c", "b", "a"])
print(pd.Index(unique_vals).get_indexer(to_match))

# 结果
[0 2 1 1 0 2]

下表给出了这几个方法的一些参考信息:

方法 说明
isin 计算一个表示”Series各值是否包含于传入的值序列中”的布尔型数组
match 计算一个数组中的各值到另一个不同值属猪的整数索引;对于数据对齐和连接类型的操作十分有用
unique 计算Series中的唯一值数组,按发现的顺序返回
value_counts 返回一个Series,其索引为唯一值,其值为频率,按计数值降序排列

有时,你可能希望得到DataFrame中多个相关列的一张柱状图。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
···
data = pd.DataFrame({Qu1': [1, 3, 4, 3, 4],
'Qu2': [2, 3, 1, 2, 3],
'Qu3': [1, 5, 2, 4, 4]})
print("-----data-----")
print(data)

# 将pandas.value_counts传给该DataFrame的apply函数,就会出现
print("柱状图")
result = data.apply(pd.value_counts).fillna(0)
print(result)

# 结果
-----data-----
Qu1 Qu2 Qu3
0 1 2 1
1 3 3 5
2 4 1 2
3 3 2 4
4 4 3 4
柱状图
Qu1 Qu2 Qu3
1 1.0 1.0 1.0
2 0.0 2.0 1.0
3 2.0 2.0 0.0
4 2.0 0.0 2.0
5 0.0 0.0 1.0

这里,结果中的行标签是所有列的唯一值。后面的频率值是每个列中这些值的相应计数。