08 章: 数据操作

引言:数据整理 - 连接、合并和重塑 🗂️

  • 在许多应用中,数据分散在多个文件或数据库中。
  • 数据的组织形式也可能不利于分析。
  • 本章重点介绍用于有效组合、连接和重新排列数据的工具。
  • 关键概念:pandas 中的分层索引 (Hierarchical Indexing)。

引言:数据整理可视化

数据整理、数据挖掘和机器学习

什么是数据整理?

  • 数据整理(或数据重整)是将数据从一种“原始”数据形式转换和映射为另一种格式的过程。
  • 目的是使其更适合和有价值地用于各种下游目的,例如分析。

数据挖掘和机器学习

什么是数据挖掘?

  • 数据挖掘 是在大型数据集中发现模式、异常和相关性以预测结果的过程。

什么是机器学习?

  • 机器学习 是人工智能的一个子领域。
  • 它专注于开发可以从数据中学习并根据数据做出决策/预测的系统。
  • 监督学习:使用标记数据集来训练算法。
  • 无监督学习:在未标记数据中发现隐藏模式。
  • 强化学习:智能体通过与环境交互来学习。

Pandas 中的分层索引

  • pandas 的一项基本功能。
  • 允许在一个轴上拥有多个(两个或更多)索引级别。
  • 允许以较低维度的形式处理较高维度的数据。
  • 类比: 可以把它想象成在文件柜中,类别下有子类别。🗄️
  • 优点: 提供了一种结构化的方式来表示和操作复杂数据集。

分层索引:示例

import pandas as pd  # 导入 pandas 库,并将其简称为 pd
import numpy as np   # 导入 numpy 库,并将其简称为 np

#| echo: true
data = pd.Series(np.random.uniform(size=9),  # 创建一个包含 9 个均匀分布随机数的 Series
                 index=[["a", "a", "a", "b", "b", "c", "c", "d", "d"],  # 外层索引
                        [1, 2, 3, 1, 3, 1, 2, 2, 3]])  # 内层索引
data  # 显示 Series
a  1    0.418707
   2    0.262342
   3    0.563146
b  1    0.150933
   3    0.566200
c  1    0.779232
   2    0.460388
d  2    0.745868
   3    0.686103
dtype: float64
  • 我们创建了一个 Series,其索引为列表的列表
  • 这将创建一个 MultiIndex 对象。
  • 索引显示中的“间隙”表示“使用正上方的标签”。

理解 MultiIndex

data.index  # 显示 Series 的索引
MultiIndex([('a', 1),
            ('a', 2),
            ('a', 3),
            ('b', 1),
            ('b', 3),
            ('c', 1),
            ('c', 2),
            ('d', 2),
            ('d', 3)],
           )
  • MultiIndex 对象表示分层索引。
  • 它包含 (外层, 内层) 的元组。
  • 这里:(‘a’, 1), (‘a’, 2), (‘a’, 3), (‘b’, 1) … 表示索引对。

部分索引

有了分层索引,就可以进行部分索引。 这允许简洁地选择数据子集。

data["b"]  # 选择索引为 "b" 的组
1    0.150933
3    0.566200
dtype: float64

部分索引(续)

data["b":"c"]  # 选择从 "b" 到 "c" 的组(包括 "b" 和 "c")
b  1    0.150933
   3    0.566200
c  1    0.779232
   2    0.460388
dtype: float64

部分索引(续)

data.loc[["b", "d"]]  # 选择索引为 "b" 和 "d" 的组
b  1    0.150933
   3    0.566200
d  2    0.745868
   3    0.686103
dtype: float64

从内层选择

data.loc[:, 2]  # 选择内层索引为 2 的所有数据
a    0.262342
c    0.460388
d    0.745868
dtype: float64
  • 我们使用 .loc 进行基于标签的索引。
  • : 选择所有外层。
  • 2 选择内层索引等于 2 的数据。

unstack()stack()

  • unstack(): 将数据重塑为 DataFrame。它将(行)索引的一个级别“透视”为列标签。
  • stack(): unstack() 的逆操作。它将列标签透视为(行)MultiIndex 中的一个级别。

unstack() 示例

data.unstack()  # 将 Series 转换为 DataFrame,内层索引变为列
1 2 3
a 0.418707 0.262342 0.563146
b 0.150933 NaN 0.566200
c 0.779232 0.460388 NaN
d NaN 0.745868 0.686103

stack() 示例

data.unstack().stack()  # 将 DataFrame 转换回 Series,列变回内层索引
a  1    0.418707
   2    0.262342
   3    0.563146
b  1    0.150933
   3    0.566200
c  1    0.779232
   2    0.460388
d  2    0.745868
   3    0.686103
dtype: float64

DataFrame 的分层索引

  • 行和列都可以有分层索引。
frame = pd.DataFrame(np.arange(12).reshape((4, 3)),  # 创建一个 4x3 的 DataFrame,值为 0-11
                     index=[["a", "a", "b", "b"], [1, 2, 1, 2]],  # 行索引:两层
                     columns=[["Ohio", "Ohio", "Colorado"],  # 列索引:两层
                              ["Green", "Red", "Green"]])
frame  # 显示 DataFrame
Ohio Colorado
Green Red Green
a 1 0 1 2
2 3 4 5
b 1 6 7 8
2 9 10 11
  • 这里,行和列都有两个级别。

命名索引级别

frame.index.names = ["key1", "key2"]  # 设置行索引的名称
frame.columns.names = ["state", "color"]  # 设置列索引的名称
frame  # 显示 DataFrame
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
  • 为索引级别命名可以提高可读性。
  • frame.index.namesframe.columns.names 设置名称。

Note

请注意,索引名称 “state” 和 “color” 不是行标签(frame.index 值)的一部分。

访问 nlevels 属性

frame.index.nlevels #查看行索引的层数
2
  • 您可以通过访问索引的 nlevels 属性来查看索引的层数。

部分列索引

类似于行索引,我们也可以选择列组:

frame["Ohio"]  # 选择列索引为 "Ohio" 的组
color Green Red
key1 key2
a 1 0 1
2 3 4
b 1 6 7
2 9 10

重新排序和排序级别

  • swaplevel(): 交换两个级别。
  • sort_index(): 使用索引级别对数据进行排序。可以按特定级别排序。

swaplevel() 示例

frame.swaplevel("key1", "key2")  # 交换行索引的 "key1" 和 "key2" 级别
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() 示例

frame.sort_index(level=1)  # 按行索引的第 1 级(key2)排序
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

swaplevel()sort_index() 组合

frame.swaplevel(0, 1).sort_index(level=0)  # 先交换行索引的 0 和 1 级,然后按第 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

Note

如果索引从最外层开始按字典顺序排序,则对分层索引对象进行数据选择的性能会更好。

按级别计算汇总统计

许多描述性和汇总统计都有一个 level 选项:

frame.groupby(level="key2").sum()  # 按行索引的 "key2" 级别分组,并计算每组的和
state Ohio Colorado
color Green Red Green
key2
1 6 8 10
2 12 14 16

按级别计算汇总统计(列示例)

frame.groupby(level="color", axis="columns").sum()  # 按列索引的 "color" 级别分组,并计算每组的和
color Green Red
key1 key2
a 1 2 1
2 8 4
b 1 14 7
2 20 10

使用 DataFrame 的列进行索引

  • set_index(): 使用一个或多个列作为索引创建一个新的 DataFrame。
  • reset_index(): 将分层索引级别移动到列中(与 set_index() 相反)。

示例 DataFrame

frame = pd.DataFrame({"a": range(7),  # 创建一个 DataFrame,列 "a" 的值为 0-6
                      "b": range(7, 0, -1),  # 列 "b" 的值为 7-1
                      "c": ["one", "one", "one", "two", "two",
                            "two", "two"],  # 列 "c" 的值为字符串
                      "d": [0, 1, 2, 0, 1, 2, 3]})  # 列 "d" 的值为 0-3
frame  # 显示 DataFrame
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

set_index() 示例

frame2 = frame.set_index(["c", "d"])  # 使用 "c" 和 "d" 列作为索引
frame2  # 显示新的 DataFrame
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
  • 我们使用 “c” 和 “d” 列创建一个 MultiIndex。
  • 默认情况下,用于索引的列会被删除。使用 drop=False 来保留它们。

set_index()drop=False

frame.set_index(["c", "d"], drop=False)  # 使用 "c" 和 "d" 列作为索引,并保留这些列
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

reset_index() 示例

frame2.reset_index()  # 将分层索引移动到列中,并创建一个默认的整数索引
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
  • reset_index() 将分层索引移动到列中。
  • 它创建一个默认的整数索引。

合并和拼接数据集

在 pandas 中合并数据的主要三种方法:

  1. pandas.merge: 基于(类似于 SQL 连接)连接 DataFrame 中的行。
  2. pandas.concat: 沿着轴连接或“堆叠”对象。
  3. combine_first: 拼接重叠数据(填充缺失值)。

数据库风格的 DataFrame 连接

  • pandas.merge 是连接操作的主要函数。
  • 类似于 SQL 连接。
  • 关键概念:(用于链接行的列)。

pandas.merge: 多对一连接(设置)

df1 = pd.DataFrame({"key": ["b", "b", "a", "c", "a", "a", "b"],  # DataFrame 1,包含 "key" 列
                    "data1": pd.Series(range(7), dtype="Int64")})  # 和 "data1" 列
df2 = pd.DataFrame({"key": ["a", "b", "d"],  # DataFrame 2,包含 "key" 列
                    "data2": pd.Series(range(3), dtype="Int64")})  # 和 "data2" 列
df1  # 显示 df1
key data1
0 b 0
1 b 1
2 a 2
3 c 3
4 a 4
5 a 5
6 b 6
df2  # 显示 df2
key data2
0 a 0
1 b 1
2 d 2

pandas.merge: 多对一连接(示例)

pd.merge(df1, df2)  # 合并 df1 和 df2,自动使用 "key" 列作为连接键
# 或者 pd.merge(df1, df2, on="key")  # 显式指定连接键为 "key"
key data1 data2
0 b 0 1
1 b 1 1
2 a 2 0
3 a 4 0
4 a 5 0
5 b 6 1
  • df1 有多行标记为 ‘a’ 和 ‘b’。
  • df2 在 ‘key’ 列中每个值只有一行。
  • 如果未指定连接列,merge 将使用重叠的列名。最佳做法是显式指定。

pandas.merge: 不同的列名

如果列名不同,请分别指定它们:

df3 = pd.DataFrame({"lkey": ["b", "b", "a", "c", "a", "a", "b"],  # DataFrame 3,包含 "lkey" 列
                    "data1": pd.Series(range(7), dtype="Int64")})  # 和 "data1" 列
df4 = pd.DataFrame({"rkey": ["a", "b", "d"],  # DataFrame 4,包含 "rkey" 列
                    "data2": pd.Series(range(3), dtype="Int64")})  # 和 "data2" 列
pd.merge(df3, df4, left_on="lkey", right_on="rkey")  # 使用 "lkey" 和 "rkey" 作为连接键
lkey data1 rkey data2
0 b 0 b 1
1 b 1 b 1
2 a 2 a 0
3 a 4 a 0
4 a 5 a 0
5 b 6 b 1
  • left_on: 左侧 DataFrame 中的列。
  • right_on: 右侧 DataFrame 中的列。

pandas.merge: 连接类型

  • 默认情况下,merge 执行 “inner” 连接(键的交集)。
  • 其他连接类型:“left”, “right”, “outer”。

pandas.merge: 外连接示例

pd.merge(df1, df2, how="outer")  # 执行外连接,包含所有键
key data1 data2
0 a 2 0
1 a 4 0
2 a 5 0
3 b 0 1
4 b 1 1
5 b 6 1
6 c 3 <NA>
7 d <NA> 2
  • 外连接: 键的并集。
  • 左连接: 左侧 DataFrame 中的所有键。
  • 右连接: 右侧 DataFrame 中的所有键。

使用 how 参数的连接类型

选项 行为
how="inner" 仅使用两个表中都存在的键组合
how="left" 使用左侧表中的所有键组合
how="right" 使用右侧表中的所有键组合
how="outer" 使用两个表中一起存在的所有键组合

pandas.merge: 多对多连接

  • 多对多连接形成匹配键的笛卡尔积
df1 = pd.DataFrame({"key": ["b", "b", "a", "c", "a", "b"],  # DataFrame 1
                    "data1": pd.Series(range(6), dtype="Int64")})
df2 = pd.DataFrame({"key": ["a", "b", "a", "b", "d"],  # DataFrame 2
                    "data2": pd.Series(range(5), dtype="Int64")})

pd.merge(df1, df2, on="key", how="left")  # 左连接
key data1 data2
0 b 0 1
1 b 0 3
2 b 1 1
3 b 1 3
4 a 2 0
5 a 2 2
6 c 3 <NA>
7 a 4 0
8 a 4 2
9 b 5 1
10 b 5 3
  • df1 中有三个 “b” 行,df2 中有两个,因此结果中有六个 “b” 行。

使用多个键合并

传递一个列名列表:

left = pd.DataFrame({"key1": ["foo", "foo", "bar"],  # 左侧 DataFrame
                     "key2": ["one", "two", "one"],
                     "lval": pd.Series([1, 2, 3], dtype='Int64')})
right = pd.DataFrame({"key1": ["foo", "foo", "bar", "bar"],  # 右侧 DataFrame
                      "key2": ["one", "one", "one", "two"],
                      "rval": pd.Series([4, 5, 6, 7], dtype='Int64')})
pd.merge(left, right, on=["key1", "key2"], how="outer")  # 使用 "key1" 和 "key2" 作为连接键,执行外连接
key1 key2 lval rval
0 bar one 3 6
1 bar two <NA> 7
2 foo one 1 4
3 foo one 1 5
4 foo two 2 <NA>
  • 可以将多个键视为形成用作单个连接键的元组。

重叠的列名

  • merge 有一个 suffixes 选项来处理重叠的列名。
pd.merge(left, right, on="key1")  # 自动添加后缀 "_x" 和 "_y"
key1 key2_x lval key2_y rval
0 foo one 1 one 4
1 foo one 1 one 5
2 foo two 2 one 4
3 foo two 2 one 5
4 bar one 3 one 6
5 bar one 3 two 7

重叠的列名:suffixes

pd.merge(left, right, on="key1", suffixes=("_left", "_right"))  # 自定义后缀
key1 key2_left lval key2_right rval
0 foo one 1 one 4
1 foo one 1 one 5
2 foo two 2 one 4
3 foo two 2 one 5
4 bar one 3 one 6
5 bar one 3 two 7

pandas.merge 函数参数

参数 描述
left 左侧要合并的 DataFrame。
right 右侧要合并的 DataFrame。
how 连接类型:“inner”, “outer”, “left”, 或 “right”(默认为 “inner”)。
on 要连接的列名(必须在两个 DataFrame 中都存在)。
left_on 左侧 DataFrame 中用作连接键的列。
right_on 类似于 left_on,用于右侧 DataFrame。
left_index 使用左侧 DataFrame 的行索引作为其连接键。
right_index 类似于 left_index
sort 按连接键对合并后的数据进行字典排序(默认为 False)。
suffixes 要附加到重叠列名的字符串元组(默认为 (“_x”, “_y”))。
copy 如果为 False,则在某些情况下避免复制数据(默认为复制)。
validate 检查合并的类型(一对一,一对多,多对多)
indicator 添加名为 _merge 的列,指示每行的来源(“left_only”, “right_only”, “both”)。

基于索引合并

  • 使用 left_index=Trueright_index=True(或两者)来基于索引合并。
left1 = pd.DataFrame({"key": ["a", "b", "a", "a", "b", "c"],  # 左侧 DataFrame
                      "value": pd.Series(range(6), dtype="Int64")})
right1 = pd.DataFrame({"group_val": [3.5, 7]}, index=["a", "b"])  # 右侧 DataFrame,使用索引

pd.merge(left1, right1, left_on="key", right_index=True)  # 将 left1 的 "key" 列与 right1 的索引合并
key value group_val
0 a 0 3.5
1 b 1 7.0
2 a 2 3.5
3 a 3 3.5
4 b 4 7.0
  • 我们将 left1 的 “key” 列与 right1 的索引合并。
  • left1的索引会被保留。

分层索引:多键合并

对于分层索引,基于索引连接类似于多键合并:

lefth = pd.DataFrame({"key1": ["Ohio", "Ohio", "Ohio",
                            "Nevada", "Nevada"],  # 左侧 DataFrame
                    "key2": [2000, 2001, 2002, 2001, 2002],
                    "data": pd.Series(range(5), dtype="Int64")})
righth_index = pd.MultiIndex.from_arrays([  # 右侧 DataFrame 的 MultiIndex
    ["Nevada", "Nevada", "Ohio", "Ohio", "Ohio", "Ohio"],
    [2001, 2000, 2000, 2000, 2001, 2002]
    ])
righth = pd.DataFrame({"event1": pd.Series([0, 2, 4, 6, 8, 10], dtype="Int64",
                                        index=righth_index),  # 使用 MultiIndex
                    "event2": pd.Series([1, 3, 5, 7, 9, 11], dtype="Int64",
                                        index=righth_index)})

pd.merge(lefth, righth, left_on=["key1", "key2"], right_index=True, how="outer")  # 基于多个列和索引合并
key1 key2 data event1 event2
4 Nevada 2000 <NA> 2 3
3 Nevada 2001 3 0 1
4 Nevada 2002 4 <NA> <NA>
0 Ohio 2000 0 4 5
0 Ohio 2000 0 6 7
1 Ohio 2001 1 8 9
2 Ohio 2002 2 10 11

DataFrame 的 join 方法

  • 简化了基于索引的合并。
  • 默认执行左连接
left2 = pd.DataFrame([[1., 2.], [3., 4.], [5., 6.]],
                     index=["a", "c", "e"],  # 左侧 DataFrame
                     columns=["Ohio", "Nevada"]).astype("Int64")
right2 = pd.DataFrame([[7., 8.], [9., 10.], [11., 12.], [13, 14]],
                      index=["b", "c", "d", "e"],  # 右侧 DataFrame
                      columns=["Missouri", "Alabama"]).astype("Int64")
left2.join(right2, how="outer")  # 基于索引连接
Ohio Nevada Missouri Alabama
a 1 2 <NA> <NA>
b <NA> <NA> 7 8
c 3 4 9 10
d <NA> <NA> 11 12
e 5 6 13 14
  • 可以基于调用 DataFrame 的其中一列进行连接。
  • 支持连接具有相似索引但列不重叠的多个 DataFrame。

join 基于列

left1.join(right1, on="key")  # 将 left1 与 right1 基于 left1 的 "key" 列连接
key value group_val
0 a 0 3.5
1 b 1 7.0
2 a 2 3.5
3 a 3 3.5
4 b 4 7.0
5 c 5 NaN

沿轴连接

  • numpy.concatenate: 适用于 NumPy 数组。
arr = np.arange(12).reshape((3, 4))  # 创建一个 3x4 的数组
np.concatenate([arr, arr], axis=1)  # 沿着列连接
array([[ 0,  1,  2,  3,  0,  1,  2,  3],
       [ 4,  5,  6,  7,  4,  5,  6,  7],
       [ 8,  9, 10, 11,  8,  9, 10, 11]])
  • pandas.concat: 解决以下问题:
    • 处理不同的索引。
    • 识别连接的块。
    • 保留数据。

pandas.concat 与 Series

s1 = pd.Series([0, 1], index=["a", "b"], dtype="Int64")  # Series 1
s2 = pd.Series([2, 3, 4], index=["c", "d", "e"], dtype="Int64")  # Series 2
s3 = pd.Series([5, 6], index=["f", "g"], dtype="Int64")  # Series 3
pd.concat([s1, s2, s3])  # 沿着行连接 Series
a    0
b    1
c    2
d    3
e    4
f    5
g    6
dtype: Int64
  • 默认情况下,concat 沿着 axis="index"(行)工作,生成另一个 Series。
  • 将值和索引粘合在一起。

pandas.concat: axis="columns"

pd.concat([s1, s2, s3], axis="columns")  # 沿着列连接 Series,生成 DataFrame
0 1 2
a 0 <NA> <NA>
b 1 <NA> <NA>
c <NA> 2 <NA>
d <NA> 3 <NA>
e <NA> 4 <NA>
f <NA> <NA> 5
g <NA> <NA> 6
  • axis="columns" 生成一个 DataFrame。
  • 结果是索引的并集(外连接)。

pandas.concat: join="inner"

s4 = pd.concat([s1, s3])  # 连接 s1 和 s3
pd.concat([s1, s4], axis="columns", join="inner")  # 沿着列连接 s1 和 s4,执行内连接
0 1
a 0 0
b 1 1
  • join="inner" 对索引执行交集。

pandas.concat: keys 参数

result = pd.concat([s1, s1, s3], keys=["one", "two", "three"])  # 连接并创建分层索引
result  # 显示结果
one    a    0
       b    1
two    a    0
       b    1
three  f    5
       g    6
dtype: Int64

unstack() 结果

result.unstack()  # 将结果转换为 DataFrame
a b f g
one 0 1 <NA> <NA>
two 0 1 <NA> <NA>
three <NA> <NA> 5 6
  • keys 创建一个分层索引。标识连接的块。
  • 当沿着 axis="columns" 连接时,keys 成为 DataFrame 列标题。

pandas.concat 与 DataFrame

逻辑与 Series 相同:

df1 = pd.DataFrame(np.arange(6).reshape(3, 2), index=["a", "b", "c"],  # DataFrame 1
                   columns=["one", "two"])
df2 = pd.DataFrame(5 + np.arange(4).reshape(2, 2), index=["a", "c"],  # DataFrame 2
                   columns=["three", "four"])
pd.concat([df1, df2], axis="columns", keys=["level1", "level2"])  # 沿着列连接,并使用 keys 作为列标题
level1 level2
one two three four
a 0 1 5.0 6.0
b 2 3 NaN NaN
c 4 5 7.0 8.0
  • 您可以使用 names 参数命名创建的轴级别。
  • 如果行索引不包含相关数据,请使用 ignore_index=True

pandas.concat 函数参数

参数 描述
objs 要连接的 pandas 对象列表或字典(必需)。
axis 要连接的轴(默认为 “index”)。
join “inner” 或 “outer”(默认为 “outer”)。
keys 与要连接的对象关联的值,形成一个分层索引。
levels 用作分层索引级别的特定索引。
names 创建的分层级别的名称。
verify_integrity 检查新轴是否有重复项,如果有则引发异常(默认为 False)。
ignore_index 不保留索引;生成一个新的 range(total_length) 索引。

合并重叠数据

  • numpy.where: 执行面向数组的 if-else 操作。
a = pd.Series([np.nan, 2.5, 0.0, 3.5, 4.5, np.nan],  # Series a,包含 NaN 值
              index=["f", "e", "d", "c", "b", "a"])
b = pd.Series([0., np.nan, 2., np.nan, np.nan, 5.],  # Series b,包含 NaN 值
              index=["a", "b", "c", "d", "e", "f"])
np.where(pd.isna(a), b, a)  # 如果 a 中有 NaN,则使用 b 中的值,否则使用 a 中的值
array([0. , 2.5, 0. , 3.5, 4.5, 5. ])

Series.combine_first

  • Series.combine_first: 按索引对齐值并“修补”缺失数据。
a.combine_first(b)  # 使用 b 中的值填充 a 中的 NaN 值,按索引对齐
a    0.0
b    4.5
c    3.5
d    0.0
e    2.5
f    5.0
dtype: float64
  • combine_first 按索引对齐(与 np.where 不同)。

combine_first 与 DataFrame

  • combine_first 逐列工作。
  • 它使用传递对象中的数据“修补”调用对象中的缺失数据。
df1 = pd.DataFrame({"a": [1., np.nan, 5., np.nan],  # DataFrame 1,包含 NaN 值
                    "b": [np.nan, 2., np.nan, 6.],
                    "c": range(2, 18, 4)})
df2 = pd.DataFrame({"a": [5., 4., np.nan, 3., 7.],  # DataFrame 2,包含 NaN 值
                    "b": [np.nan, 3., 4., 6., 8.]})
df1.combine_first(df2)  # 使用 df2 中的值填充 df1 中的 NaN 值,逐列进行
a b c
0 1.0 NaN 2.0
1 4.0 2.0 6.0
2 5.0 4.0 10.0
3 3.0 6.0 14.0
4 7.0 8.0 NaN

重塑和透视

  • 重塑/透视操作: 重新排列表格数据。
  • 分层索引 提供了一种一致的方式来重塑。
  • 两个主要操作:
    • stack: 将列“旋转”或透视到行。
    • unstack: 将行透视到列。

stackunstack: 示例 DataFrame

data = pd.DataFrame(np.arange(6).reshape((2, 3)),  # 创建一个 2x3 的 DataFrame
                    index=pd.Index(["Ohio", "Colorado"], name="state"),  # 行索引,名为 "state"
                    columns=pd.Index(["one", "two", "three"],
                    name="number"))  # 列索引,名为 "number"
data  # 显示 DataFrame
number one two three
state
Ohio 0 1 2
Colorado 3 4 5

stack 示例

result = data.stack()  # 将 DataFrame 转换为 Series,列索引变为内层行索引
result  # 显示 Series
state     number
Ohio      one       0
          two       1
          three     2
Colorado  one       3
          two       4
          three     5
dtype: int64

unstack 示例

result.unstack()  # 将 Series 转换回 DataFrame,内层行索引变为列索引
number one two three
state
Ohio 0 1 2
Colorado 3 4 5

unstack 与不同的级别

  • 默认情况下,最内层被 unstacked。
  • 通过数字或名称指定不同的级别。
result.unstack(level=0)  # 将外层行索引("state")unstack 到列
# 或 result.unstack("state")  # 使用名称指定要 unstack 的级别
state Ohio Colorado
number
one 0 3
two 1 4
three 2 5
  • Unstacking 可能会引入缺失数据。
  • Stacking 默认会过滤掉缺失数据。

Stacking 与 dropna=False

s1 = pd.Series([0, 1, 2, 3], index=["a", "b", "c", "d"], dtype="Int64")  # Series 1
s2 = pd.Series([4, 5, 6], index=["c", "d", "e"], dtype="Int64")  # Series 2
data2 = pd.concat([s1, s2], keys=["one", "two"])  # 连接 s1 和 s2,并创建分层索引
data2.unstack().stack(dropna=False)  # 先 unstack,再 stack,并保留 NaN 值
one  a       0
     b       1
     c       2
     d       3
     e    <NA>
two  a    <NA>
     b    <NA>
     c       4
     d       5
     e       6
dtype: Int64

DataFrame 中的 unstack

在 DataFrame 中 unstacking 时,unstacked 的级别将成为最低级别。

df = pd.DataFrame({"left": result, "right": result + 5},  # 创建一个 DataFrame
                  columns=pd.Index(["left", "right"], name="side"))  # 列索引,名为 "side"
df  # 显示 DataFrame
side left right
state number
Ohio one 0 5
two 1 6
three 2 7
Colorado one 3 8
two 4 9
three 5 10

unstackstack 示例

df.unstack(level="state").stack(level="side")  # 先按 "state" 级别 unstack,再按 "side" 级别 stack
state Ohio Colorado
number side
one left 0 3
right 5 8
two left 1 4
right 6 9
three left 2 5
right 7 10

将“长”格式透视为“宽”格式

  • 长/堆叠格式: 常用于存储多个时间序列。每行是一个观察值。
  • 宽格式: 每个变量都有自己的列。

示例:长格式数据

data = pd.read_csv("examples/macrodata.csv")  # 从 CSV 文件读取数据
data = data.loc[:, ["year", "quarter", "realgdp", "infl", "unemp"]]  # 选择特定的列
periods = pd.PeriodIndex(year=data.pop("year"),  # 创建一个 PeriodIndex
                        quarter=data.pop("quarter"),
                        name="date")
data.index = periods.to_timestamp("D")  # 将 PeriodIndex 转换为 Timestamp,并设置为索引
data = data.reindex(columns=["realgdp", "infl", "unemp"])  # 重新设置列的顺序
data.columns.name = "item"  # 设置列索引的名称
long_data = (data.stack()  # 将 DataFrame 转换为 Series
                .reset_index()  # 重置索引
                .rename(columns={0: "value"}))  # 重命名列
long_data[:10]  # 显示前 10 行
date item value
0 1959-01-01 realgdp 2710.349
1 1959-01-01 infl 0.000
2 1959-01-01 unemp 5.800
3 1959-04-01 realgdp 2778.801
4 1959-04-01 infl 2.340
5 1959-04-01 unemp 5.100
6 1959-07-01 realgdp 2775.488
7 1959-07-01 infl 2.740
8 1959-07-01 unemp 5.300
9 1959-10-01 realgdp 2785.204

pivot 方法

pivot 方法将长格式转换为宽格式。

pivoted = long_data.pivot(index="date", columns="item",
                          values="value")  # 将长格式数据透视为宽格式
pivoted.head()  # 显示前几行
item infl realgdp unemp
date
1959-01-01 0.00 2710.349 5.8
1959-04-01 2.34 2778.801 5.1
1959-07-01 2.74 2775.488 5.3
1959-10-01 0.27 2785.204 5.6
1960-01-01 2.31 2847.699 5.2
  • index: 用作行索引的列。
  • columns: 用于创建新列的列。
  • values: 用于填充 DataFrame 的列。

使用多个值列进行透视

long_data["value2"] = np.random.standard_normal(len(long_data))  # 添加一个新的值列
pivoted = long_data.pivot(index="date", columns="item")  # 透视,不指定 values 参数
pivoted.head()  # 显示前几行,现在有分层列
value value2
item infl realgdp unemp infl realgdp unemp
date
1959-01-01 0.00 2710.349 5.8 0.896446 0.171507 -0.254807
1959-04-01 2.34 2778.801 5.1 -0.624636 -0.777783 -2.265161
1959-07-01 2.74 2775.488 5.3 -1.547161 0.284810 -1.725090
1959-10-01 0.27 2785.204 5.6 -0.780010 0.433035 -0.838295
1960-01-01 2.31 2847.699 5.2 0.685781 1.087949 1.090611
  • 如果省略 values 参数,则会得到分层列。

pivot 等价于…

pivot 等价于使用 set_index 后跟 unstack

unstacked = long_data.set_index(["date", "item"]).unstack(level="item")  # 先设置索引,再 unstack
unstacked.head()  # 显示前几行
value value2
item infl realgdp unemp infl realgdp unemp
date
1959-01-01 0.00 2710.349 5.8 0.896446 0.171507 -0.254807
1959-04-01 2.34 2778.801 5.1 -0.624636 -0.777783 -2.265161
1959-07-01 2.74 2775.488 5.3 -1.547161 0.284810 -1.725090
1959-10-01 0.27 2785.204 5.6 -0.780010 0.433035 -0.838295
1960-01-01 2.31 2847.699 5.2 0.685781 1.087949 1.090611

将“宽”格式透视为“长”格式

  • pandas.melt: pivot 的逆操作。将多列合并为一列(更长的 DataFrame)。
df = pd.DataFrame({"key": ["foo", "bar", "baz"],  # 创建一个 DataFrame
                   "A": [1, 2, 3],
                   "B": [4, 5, 6],
                   "C": [7, 8, 9]})
melted = pd.melt(df, id_vars="key")  # 将 DataFrame 从宽格式转换为长格式
melted  # 显示结果
key variable value
0 foo A 1
1 bar A 2
2 baz A 3
3 foo B 4
4 bar B 5
5 baz B 6
6 foo C 7
7 bar C 8
8 baz C 9
  • id_vars: 组指示器列。
  • value_vars: 要“取消透视”的列。如果未指定,则使用所有其他列。

pandas.melt 示例

pd.melt(df, id_vars="key", value_vars=["A", "B"])  # 指定要取消透视的列
key variable value
0 foo A 1
1 bar A 2
2 baz A 3
3 foo B 4
4 bar B 5
5 baz B 6
pd.melt(df, value_vars=["A", "B", "C"])  # 不指定组指示器列
variable value
0 A 1
1 A 2
2 A 3
3 B 4
4 B 5
5 B 6
6 C 7
7 C 8
8 C 9
pd.melt(df, value_vars=["key", "A", "B"])  # `value_vars` 也可以包含组标识符
variable value
0 key foo
1 key bar
2 key baz
3 A 1
4 A 2
5 A 3
6 B 4
7 B 5
8 B 6
  • 可以不使用任何组标识符。

使用 meltpivot 重塑

reshaped = melted.pivot(index="key", columns="variable",
                        values="value")  # 使用 pivot 将数据恢复到类似原始布局
reshaped  # 显示结果
variable A B C
key
bar 2 5 8
baz 3 6 9
foo 1 4 7
reshaped.reset_index()  # 将索引移回列中
variable key A B C
0 bar 2 5 8
1 baz 3 6 9
2 foo 1 4 7
  • pivot 可以重塑回原始布局。
  • 由于 pivot 会创建一个索引,因此 reset_index() 可能很有用。

总结 📝

  • 我们介绍了 pandas 中数据整理的关键技术:
    • 分层索引 (MultiIndex)。
    • merge (数据库风格的连接)。
    • concat (沿轴连接)。
    • combine_first (修补缺失数据)。
    • stackunstack (重塑)。
    • pivot (长格式到宽格式)。
    • melt (宽格式到长格式)。
  • 这些工具对于为分析准备数据至关重要。

思考与讨论 🤔

  • 如何在您自己的数据分析项目中应用这些技术?
  • 您能想到现实世界的例子吗?
  • 有哪些挑战或限制?
  • 您最有兴趣应用哪种方法,为什么?
  • 在什么情况下您会更喜欢 merge 而不是 concat,反之亦然?
  • 理解分层索引如何改进复杂数据集的结构化?