Python数据分析NumPy和pandas(二十四、数据整理--连接、合并和重塑 之一:分层索引)
在许多应用程序中,数据可能分布在多个文件或数据库中,或者数据的排列方式不利于分析。
下面要学习能帮助我们合并、连接和重新排列数据的工具。首先,学习 pandas 中的分层索引概念,然后逐步深入学习数据合并操作、数据重新排列操作等。
一、分层索引(Hierarchical indexing)
分层索引是 pandas 的一项重要功能,它使我们能够在一个轴上具有多个(两个或以上)索引级别。它为我们提供了一种以较低维形式处理高维数据的方法。以一个简单的示例开始,我们以一个列表或数组作为索引创建一个Series对象:
import numpy as np
import pandas as pd
# 设置一个种子,为了每次运行获取相同的随机值
rng = np.random.default_rng(seed=12345)
data = pd.Series(np.random.uniform(size=9),
index=[["a", "a", "a", "b", "b", "c", "c", "d","d"], [1, 2, 3, 1, 3, 1, 2, 2, 3]])
print(data)
输出结果:
a 1 0.474136
2 0.025017
3 0.110308
b 1 0.771097
3 0.915783
c 1 0.575484
2 0.790313
d 2 0.090362
3 0.545744
dtype: float64
从以上输出结果看到的是 Series 的美化视图,其中 MultiIndex 作为其索引。索引显示中的“间隙”表示“使用正上方的标签”。
可以用 data.index 查看这个MultiIndex :
import numpy as np
import pandas as pd
# 设置一个种子,为了每次运行获取相同的随机值
rng = np.random.default_rng(seed=12345)
data = pd.Series(np.random.uniform(size=9),
index=[["a", "a", "a", "b", "b", "c", "c", "d","d"], [1, 2, 3, 1, 3, 1, 2, 2, 3]])
print(data.index)
输出结果:
MultiIndex([('a', 1),
('a', 2),
('a', 3),
('b', 1),
('b', 3),
('c', 1),
('c', 2),
('d', 2),
('d', 3)],
)
使用分层索引的部分索引可以很方便的选择数据集的子集。例如:
data['b'] 输出
1 0.771097
3 0.915783
dtype: float64
data["b":"c"] 输出:
b 1 0.771097
3 0.915783
c 1 0.575484
2 0.790313
dtype: float64
data.loc[["b", "d"]] 输出:
b 1 0.771097
3 0.915783
d 2 0.090362
3 0.545744
dtype: float64
我们也可以从 “inner” 索引级别进行选择。下面我从第二个索引级别中选择索引值为 2 对应的元素值。data.loc[:, 2] 输出:
a 0.025017
c 0.790313
d 0.090362
dtype: float64
分层索引在重塑数据和基于组的操作(如形成数据透视表)中起着重要作用。例如,我们可以使用其 unstack() 方法将此数据重新排列到 DataFrame 中,stack()方法是unstack() 逆向操作方法:
import numpy as np
import pandas as pd
# 设置一个种子,为了每次运行获取相同的随机值
rng = np.random.default_rng(seed=12345)
data = pd.Series(np.random.uniform(size=9),
index=[["a", "a", "a", "b", "b", "c", "c", "d","d"], [1, 2, 3, 1, 3, 1, 2, 2, 3]])
print(data.unstack())
print(data.unstack().stack())
data.unstack() 输出:
1 | 2 | 3 | |
---|---|---|---|
a | 0.474136 | 0.025017 | 0.110308 |
b | 0.771097 | NaN | 0.915783 |
c | 0.575484 | 0.790313 | NaN |
d | NaN | 0.090362 | 0.545744 |
data.unstack().stack() 输出:
a 1 0.474136
2 0.025017
3 0.110308
b 1 0.771097
3 0.915783
c 1 0.575484
2 0.790313
d 2 0.090362
3 0.545744
dtype: float64
使用 DataFrame,任一轴都可以具有分层索引。同时,分层索引的每一层级可以具有名称,例如:
import numpy as np
import pandas as pd
frame = pd.DataFrame(np.arange(12).reshape((4, 3)),
index=[["a", "a", "b", "b"], [1, 2, 1, 2]],
columns=[["Ohio", "Ohio", "Colorado"], ["Green", "Red", "Green"]])
print(frame)
frame.index.names = ["key1", "key2"]
frame.columns.names = ["state", "color"]
print(frame)
设置names前frame输出:
Ohio | Colorado | |||
---|---|---|---|---|
Green | Red | Green | ||
a | 1 | 0 | 1 | 2 |
2 | 3 | 4 | 5 | |
b | 1 | 6 | 7 | 8 |
2 | 9 | 10 | 11 |
设置names后frame输出:
state | Ohio | Colorado | ||
---|---|---|---|---|
color | Green | Red | Green | |
key1 | key2 | |||
a | 1 | 0 | 1 | 2 |
2 | 3 | 4 | 5 | |
b | 1 | 6 | 7 | 8 |
2 | 9 | 10 | 11 |
从以上格式化输出我们可以看出,分层索引类似于表格的复杂表头。这些设置的names名称取代了 name 属性,该属性仅用于单级索引。这里要注意的是:索引名称 “state” 和 “color” 不是行标签(frame.index 值)的一部分。
可以通过nlevels属性查看所有有多少层,上面示例中的frame可以这样调用frame.index.nlevels输出:2。
使用部分列索引,可以同样选择列方向的子集。例如:
import numpy as np
import pandas as pd
frame = pd.DataFrame(np.arange(12).reshape((4, 3)),
index=[["a", "a", "b", "b"], [1, 2, 1, 2]],
columns=[["Ohio", "Ohio", "Colorado"], ["Green", "Red", "Green"]])
frame.index.names = ["key1", "key2"]
frame.columns.names = ["state", "color"]
frame.index.nlevels
print(frame["Ohio"])
frame["Ohio"]输出:
color | Green | Red | |
---|---|---|---|
key1 | key2 | ||
a | 1 | 0 | 1 |
2 | 3 | 4 | |
b | 1 | 6 | 7 |
2 | 9 | 10 |
MultiIndex 可以自行创建,然后复用。前面的 DataFrame 中具有级别名称的列也可以这样创建:
import numpy as np
import pandas as pd
pd.MultiIndex.from_arrays([["Ohio", "Ohio", "Colorado"],
["Green", "Red", "Green"]],
names=["state", "color"])
二、对分层级别进行排序和重新排序
有时,我们可能需要重新排列轴上层级的顺序或按一个特定级别中的值对数据进行排序。swaplevel() 方法使用两个级别数字或名称,并返回一个级别互换的新对象(但数据在其他方面保持不变)。例如:
import numpy as np
import pandas as pd
frame = pd.DataFrame(np.arange(12).reshape((4, 3)),
index=[["a", "a", "b", "b"], [1, 2, 1, 2]],
columns=[["Ohio", "Ohio", "Colorado"], ["Green", "Red", "Green"]])
frame.index.names = ["key1", "key2"]
frame.columns.names = ["state", "color"]
frame.index.nlevels
frame_swap = frame.swaplevel("key1", "key2")
print(frame_swap)
输出:
state | Ohio | Colorado | ||
---|---|---|---|---|
color | Green | Red | Green | |
key2 | key1 | |||
1 | a | 0 | 1 | 2 |
2 | a | 3 | 4 | 5 |
1 | b | 6 | 7 | 8 |
2 | b | 9 | 10 | 11 |
对于排序默认情况下,sort_index 方法使用所有索引级别按字典顺序对数据进行排序,但我们可以通过传递 level 参数来选择仅使用单个级别或级别子集进行排序。 例如:
import numpy as np
import pandas as pd
frame = pd.DataFrame(np.arange(12).reshape((4, 3)),
index=[["a", "a", "b", "b"], [1, 2, 1, 2]],
columns=[["Ohio", "Ohio", "Colorado"], ["Green", "Red", "Green"]])
frame.index.names = ["key1", "key2"]
frame.columns.names = ["state", "color"]
frame.index.nlevels
frame_swap = frame.swaplevel("key1", "key2")
print(frame.sort_index(level=1))
print(frame.swaplevel(0, 1).sort_index(level=0))
frame.sort_index(level=1) 输出:
state | Ohio | Colorado | ||
---|---|---|---|---|
color | Green | Red | Green | |
key1 | key2 | |||
a | 1 | 0 | 1 | 2 |
b | 1 | 6 | 7 | 8 |
a | 2 | 3 | 4 | 5 |
b | 2 | 9 | 10 | 11 |
frame.swaplevel(0, 1).sort_index(level=0) 输出:
state | Ohio | Colorado | ||
---|---|---|---|---|
color | Green | Red | Green | |
key2 | key1 | |||
1 | a | 0 | 1 | 2 |
b | 6 | 7 | 8 | |
2 | a | 3 | 4 | 5 |
b | 9 | 10 | 11 |
注意:如果索引从最外层开始按字典顺序排序,(即调用 sort_index(level=0) 或 sort_index() 的结果),则分层索引对象上的数据选择性能要好得多。
三、按分层级别进行的汇总统计
DataFrame 和 Series 上的许多描述性和摘要性统计信息都有一个 level 参数选项,我们可以通过level指定要在特定轴上聚合的级别。以上面的 DataFrame为例,我们可以按级别对 rows 或 columns 进行聚合:
import numpy as np
import pandas as pd
frame = pd.DataFrame(np.arange(12).reshape((4, 3)),
index=[["a", "a", "b", "b"], [1, 2, 1, 2]],
columns=[["Ohio", "Ohio", "Colorado"], ["Green", "Red", "Green"]])
frame.index.names = ["key1", "key2"]
frame.columns.names = ["state", "color"]
frame.index.nlevels
print(frame.groupby(level="key2").sum())
print(frame.groupby(level="color", axis="columns").sum())
frame.groupby(level="key2").sum() 输出:
state | Ohio | Colorado | |
---|---|---|---|
color | Green | Red | Green |
key2 | |||
1 | 6 | 8 | 10 |
2 | 12 | 14 | 16 |
frame.groupby(level="color", axis="columns").sum() 输出:
color | Green | Red | |
---|---|---|---|
key1 | key2 | ||
a | 1 | 2 | 1 |
2 | 8 | 4 | |
b | 1 | 14 | 7 |
2 | 20 | 10 |
用frame.groupby(level="color", axis="columns").sum()进行汇总,代码运行时会有一个警告FutureWarning,提示groupby方法中使用axis参数已被丢弃,但同样可以执行。这是因为我使用了最新版的pandas库。这行代码可以改为:frame.T.groupby(level="color").sum().T
四、使用 DataFrame 的列进行索引
使用 DataFrame 中的一列或多列作为行索引也是常用的操作;或者,有时候我们想将 行 索引移动到 DataFrame 的列中。DataFrame 的 set_index 函数将使用其一个或多个列作为索引创建新的 DataFrame。默认情况下,这些列将从 DataFrame 中删除,但我们可以通过将 drop=False 传递给 set_index 来保留它们。set_index函数有个逆向操作函数reset_index将分层索引级别移动到列中。我们写个示例:
import numpy as np
import pandas as pd
frame = pd.DataFrame({"a": range(7), "b": range(7, 0, -1),
"c": ["one", "one", "one", "two", "two",
"two", "two"],
"d": [0, 1, 2, 0, 1, 2, 3]})
print(frame)
frame2 = frame.set_index(["c", "d"])
print(frame2)
frame3 = frame.set_index(["c", "d"], drop=False)
print(frame3)
frame4 = frame2.reset_index()
print(frame4)
frame输出:
a | b | c | d | |
---|---|---|---|---|
0 | 0 | 7 | one | 0 |
1 | 1 | 6 | one | 1 |
2 | 2 | 5 | one | 2 |
3 | 3 | 4 | two | 0 |
4 | 4 | 3 | two | 1 |
5 | 5 | 2 | two | 2 |
6 | 6 | 1 | two | 3 |
frame2输出:
a | b | ||
---|---|---|---|
c | d | ||
one | 0 | 0 | 7 |
1 | 1 | 6 | |
2 | 2 | 5 | |
two | 0 | 3 | 4 |
1 | 4 | 3 | |
2 | 5 | 2 | |
3 | 6 | 1 |
frame3输出:
a | b | c | d | ||
---|---|---|---|---|---|
c | d | ||||
one | 0 | 0 | 7 | one | 0 |
1 | 1 | 6 | one | 1 | |
2 | 2 | 5 | one | 2 | |
two | 0 | 3 | 4 | two | 0 |
1 | 4 | 3 | two | 1 | |
2 | 5 | 2 | two | 2 | |
3 | 6 | 1 | two | 3 |
frame4输出:
c | d | a | b | |
---|---|---|---|---|
0 | one | 0 | 0 | 7 |
1 | one | 1 | 1 | 6 |
2 | one | 2 | 2 | 5 |
3 | two | 0 | 3 | 4 |
4 | two | 1 | 4 | 3 |
5 | two | 2 | 5 | 2 |
6 | two | 3 | 6 | 1 |