文章目录
《Python数据科学手册》读书笔记
合并数据集: Concat与Append操作
这既包括将
两个不同的数据集非常简单地拼接在一起, 也包括用数据库那样的连接
(join) 与合并(merge) 操作处理有重叠字段的数据集。 Series 与
DataFrame 都具备这类操作, Pandas 的函数与方法让数据合并变得快
速简单。
先来用 pd.concat 函数演示一个 Series 与 DataFrame 的简单合并操
作。
首先导入 Pandas 和 NumPy:
import pandas as pd
import numpy as np
简单起见, 定义一个能够创建 DataFrame 某种形式的函数, 后面将会
用到:
def make_df(cols, ind):
"""一个简单的DataFrame"""
data = {c: [str(c) + str(i) for i in ind]
for c in cols}
return pd.DataFrame(data, ind)
# DataFrame示例
make_df('ABC', range(3))
| A | B | C | |
|---|---|---|---|
| 0 | A0 | B0 | C0 |
| 1 | A1 | B1 | C1 |
| 2 | A2 | B2 | C2 |
知识回顾: NumPy数组的合并
合并 Series 与 DataFrame 与合并 NumPy 数组基本相同, 后者通过
np.concatenate 函数即可完成。 你可以用这个函数将
两个或两个以上的数组合并成一个数组。
x = [1, 2, 3]
y = [4, 5, 6]
z = [7, 8, 9]
np.concatenate([x, y, z])
array([1, 2, 3, 4, 5, 6, 7, 8, 9])
第一个参数是需要合并的数组列表或元组。 还有一个 axis 参数可以设
置合并的坐标轴方向:
x = [[1, 2],
[3, 4]]
np.concatenate([x, x], axis=1)
array([[1, 2, 1, 2],
[3, 4, 3, 4]])
通过pd.concat实现简易合并
Pandas 有一个 pd.concat() 函数与 np.concatenate 语法类似, 但是
配置参数更多, 功能也更强大:
Pandas 0.18版中的函数签名
pd.concat(objs, axis=0, join=‘outer’, join_axes=None, ignore_index=False,
keys=None, levels=None, names=None, verify_integrity=False,
copy=True)
pd.concat() 可以简单地合并一维的 Series 或 DataFrame 对象, 与
np.concatenate() 合并数组一样:
ser1 = pd.Series(['A', 'B', 'C'], index=[1, 2, 3])
ser2 = pd.Series(['D', 'E', 'F'], index=[4, 5, 6])
pd.concat([ser1, ser2])
1 A
2 B
3 C
4 D
5 E
6 F
dtype: object
它也可以用来合并高维数据, 例如下面的 DataFrame:
df1 = make_df('AB', [1, 2])
df2 = make_df('AB', [3, 4])
print(df1); print(df2); print(pd.concat([df1, df2]))
A B
1 A1 B1
2 A2 B2
A B
3 A3 B3
4 A4 B4
A B
1 A1 B1
2 A2 B2
3 A3 B3
4 A4 B4
默认情况下, DataFrame 的合并都是逐行进行的(默认设置是
axis=0) 。 与 np.concatenate() 一样, pd.concat 也可以设置合并
坐标轴, 例如下面的示例:
df3 = make_df('AB', [0, 1])
df4 = make_df('CD', [0, 1])
print(df3); print(df4); print(pd.concat([df3, df4], axis=1))
A B
0 A0 B0
1 A1 B1
C D
0 C0 D0
1 C1 D1
A B C D
0 A0 B0 C0 D0
1 A1 B1 C1 D1
- 索引重复
np.concatenate 与 pd.concat 最主要的差异之一就是 Pandas 在合并时会保留索引
x = make_df('AB', [0, 1])
y = make_df('AB', [2, 3])
y.index = x.index # 复制索引
print(x); print(y); print(pd.concat([x, y]))
A B
0 A0 B0
1 A1 B1
A B
0 A2 B2
1 A3 B3
A B
0 A0 B0
1 A1 B1
0 A2 B2
1 A3 B3
结果中的索引是重复的。 虽然 DataFrame 允许这么做,
但结果并不是我们想要的。 pd.concat() 提供了一些解决这个问
题的方法。
- (1) 捕捉索引重复的错误。 如果你想要检测 pd.concat() 合并的结
果中是否出现了重复的索引, 可以设置 verify_integrity 参
数。 将参数设置为 True, 合并时若有索引重复就会触发异常。 下
面的示例可以让我们清晰地捕捉并打印错误信息:
try:
pd.concat([x, y], verify_integrity=True)
except ValueError as e:
print("ValueError:", e)
ValueError: Indexes have overlapping values: Int64Index([0, 1], dtype='int64')
- (2) 忽略索引。 有时索引无关紧要, 那么合并时就可以忽略它们,
可以通过设置 ignore_index 参数来实现。 如果将参数设置为
True, 那么合并时将会创建一个新的整数索引。
print(x); print(y); print(pd.concat([x, y], ignore_index=True))
A B
0 A0 B0
1 A1 B1
A B
0 A2 B2
1 A3 B3
A B
0 A0 B0
1 A1 B1
2 A2 B2
3 A3 B3
- (3) 增加多级索引。 另一种处理索引重复的方法是通过 keys 参数
为数据源设置多级索引标签, 这样结果数据就会带上多级索引:
示例合并后的结果是多级索引的 DataFrame
- 类似join的合并
前面介绍的简单示例都有一个共同特点, 那就是合并的 DataFrame
都是同样的列名。 而在实际工作中, 需要合并的数据往往带有不同
的列名, 而 pd.concat 提供了一些选项来解决这类合并问题。
df5 = make_df('ABC', [1, 2])
df6 = make_df('BCD', [3, 4])
print(df5); print(df6); print(pd.concat([df5, df6]))
A B C
1 A1 B1 C1
2 A2 B2 C2
B C D
3 B3 C3 D3
4 B4 C4 D4
A B C D
1 A1 B1 C1 NaN
2 A2 B2 C2 NaN
3 NaN B3 C3 D3
4 NaN B4 C4 D4
C:\ProgramData\Anaconda3\lib\site-packages\ipykernel_launcher.py:3: FutureWarning: Sorting because non-concatenation axis is not aligned. A future version
of pandas will change to not sort by default.
To accept the future behavior, pass 'sort=False'.
To retain the current behavior and silence the warning, pass 'sort=True'.
This is separate from the ipykernel package so we can avoid doing imports until
默认情况下, 某个位置上缺失的数据会用 NaN 表示。 如果不想这
样, 可以用 join 和 join_axes 参数设置合并方式。 默认的合并方
式是对所有输入列进行并集合并(join=‘outer’) , 当然也可以
用 join=‘inner’ 实现对输入列的交集合并:
print(df5); print(df6);
print(pd.concat([df5, df6], join='inner'))
A B C
1 A1 B1 C1
2 A2 B2 C2
B C D
3 B3 C3 D3
4 B4 C4 D4
B C
1 B1 C1
2 B2 C2
3 B3 C3
4 B4 C4
另一种合并方式是直接确定结果使用的列名, 设置 join_axes 参
数, 里面是索引对象构成的列表(是列表的列表) 。 如下面示例所
示, 将结果的列名设置为第一个输入的列名:
print(df5); print(df6);
print(pd.concat([df5, df6], join_axes=[df5.columns]))
A B C
1 A1 B1 C1
2 A2 B2 C2
B C D
3 B3 C3 D3
4 B4 C4 D4
A B C
1 A1 B1 C1
2 A2 B2 C2
3 NaN B3 C3
4 NaN B4 C4
- append()方法
因为直接进行数组合并的需求非常普遍, 所以 Series 和
DataFrame 对象都支持 append 方法, 让你通过最少的代码实现合
并功能。 例如, 你可以使用 df1.append(df2), 效果与
pd.concat([df1, df2]) 一样:
print(df1); print(df2); print(df1.append(df2))
A B
1 A1 B1
2 A2 B2
A B
3 A3 B3
4 A4 B4
A B
1 A1 B1
2 A2 B2
3 A3 B3
4 A4 B4
需要注意的是, 与 Python 列表中的 append() 和 extend() 方法不
同, Pandas 的 append() 不直接更新原有对象的值, 而是为合并后
的数据创建一个新对象。 因此, 它不能被称之为一个非常高效的解
决方案, 因为每次合并都需要重新创建索引和数据缓存。 总之, 如
果你需要进行多个 append 操作, 还是建议先创建一个 DataFrame
列表, 然后用 concat() 函数一次性解决所有合并任务。
合并数据集: 合并与连接
pd.merge() 实现的功能基于关系代数(relational algebra) 的一部分。
关系代数是处理关系型数据的通用理论, 绝大部分数据库的可用操作都
以此为理论基础。 关系代数方法论的强大之处在于, 它提出的若干简单
操作规则经过组合就可以为任意数据集构建十分复杂的操作。 借助在数
据库或程序里已经高效实现的基本操作规则, 你可以完成许多非常复杂
的操作。
数据连接的类型
pd.merge() 函数实现了三种数据连接的类型: 一对一、 多对一和多对
多。 这三种数据连接类型都通过 pd.merge() 接口进行调用, 根据不同
的数据连接需求进行不同的操作。 下面将通过一些示例来演示这三种类
型, 并进一步介绍更多的细节。
- 一对一连接
一对一连接可能是最简单的数据合并类型了, 与 按列
合并十分相似。 如下面示例所示, 有两个包含同一所公司员工不同
信息的 DataFrame:
df1 = pd.DataFrame({'employee': ['Bob', 'Jake', 'Lisa', 'Sue'],
'group': ['Accounting', 'Engineering', 'Engineering', 'HR']})
df2 = pd.DataFrame({'employee': ['Lisa', 'Bob', 'Jake', 'Sue'],
'hire_date': [2004, 2008, 2012, 2014]})
print(df1)
print(df2)
employee group
0 Bob Accounting
1 Jake Engineering
2 Lisa Engineering
3 Sue HR
employee hire_date
0 Lisa 2004
1 Bob 2008
2 Jake 2012
3 Sue 2014
若想将这两个 DataFrame 合并成一个 DataFrame, 可以用
pd.merge() 函数实现:
df3 = pd.merge(df1, df2)
df3
| employee | group | hire_date | |
|---|---|---|---|
| 0 | Bob | Accounting | 2008 |
| 1 | Jake | Engineering | 2012 |
| 2 | Lisa | Engineering | 2004 |
| 3 | Sue | HR | 2014 |
pd.merge() 方法会发现两个 DataFrame 都有“employee”列, 并会
自动以这列作为键进行连接。 两个输入的合并结果是一个新的
DataFrame。 需要注意的是, 共同列的位置可以是不一致的。 例如
在这个例子中, 虽然 df1 与 df2 中“employee”列的位置是不一样
的, 但是 pd.merge() 函数会正确处理这个问题。 另外还需要注意
的是, pd.merge() 会默认丢弃原来的行索引, 不过也可以自定义。
- 多对一连接
多对一连接是指, 在需要连接的两个列中, 有一列的值有重复。 通
过多对一连接获得的结果 DataFrame 将会保留重复值。
df4 = pd.DataFrame({'group': ['Accounting', 'Engineering', 'HR'],
'supervisor': ['Carly', 'Guido', 'Steve']})
print(df3); print(df4); print(pd.merge(df3, df4))
employee group hire_date
0 Bob Accounting 2008
1 Jake Engineering 2012
2 Lisa Engineering 2004
3 Sue HR 2014
group supervisor
0 Accounting Carly
1 Engineering Guido
2 HR Steve
employee group hire_date supervisor
0 Bob Accounting 2008 Carly
1 Jake Engineering 2012 Guido
2 Lisa Engineering 2004 Guido
3 Sue HR 2014 Steve
在结果 DataFrame 中多了一个“supervisor”列, 里面有些值会因为
输入数据的对应关系而有所重复。
- 多对多连接
多对多连接是个有点儿复杂的概念, 不过也可以理解。 如果左右两
个输入的共同列都包含重复值, 那么合并的结果就是一种多对多连
接。 用一个例子来演示可能更容易理解。 来看下面的例子, 里面有
一个 DataFrame 显示不同岗位人员的一种或多种能力。
通过多对多链接, 就可以得知每位员工所具备的能力:
df5 = pd.DataFrame({'group': ['Accounting', 'Accounting',
'Engineering', 'Engineering', 'HR', 'HR'],
'skills': ['math', 'spreadsheets', 'coding', 'linux',
'spreadsheets', 'organization']})
print(df1); print(df5); print(pd.merge(df1, df5))
employee group
0 Bob Accounting
1 Jake Engineering
2 Lisa Engineering
3 Sue HR
group skills
0 Accounting math
1 Accounting spreadsheets
2 Engineering coding
3 Engineering linux
4 HR spreadsheets
5 HR organization
employee group skills
0 Bob Accounting math
1 Bob Accounting spreadsheets
2 Jake Engineering coding
3 Jake Engineering linux
4 Lisa Engineering coding
5 Lisa Engineering linux
6 Sue HR spreadsheets
7 Sue HR organization
设置数据合并的键
pd.merge() 的默认行为: 它会将两个输入的一个或多个
共同列作为键进行合并。 但由于两个输入要合并的列通常都不是同名
的, 因此 pd.merge() 提供了一些参数处理这个问题。
- 参数on的用法
最简单的方法就是直接将参数 on 设置为一个列名字符串或者一个
包含多列名称的列表:
print(df1); print(df2); print(pd.merge(df1, df2, on='employee'))
employee group
0 Bob Accounting
1 Jake Engineering
2 Lisa Engineering
3 Sue HR
employee hire_date
0 Lisa 2004
1 Bob 2008
2 Jake 2012
3 Sue 2014
employee group hire_date
0 Bob Accounting 2008
1 Jake Engineering 2012
2 Lisa Engineering 2004
3 Sue HR 2014
这个参数只能在两个 DataFrame 有共同列名的时候才可以使用。
- left_on与right_on参数
有时也需要合并两个列名不同的数据集, 例如前面的员工信息表
中有一个字段不是“employee”而是“name”。 在这种情况下, 就可以
用 left_on 和 right_on 参数来指定列名:
df3 = pd.DataFrame({'name': ['Bob', 'Jake', 'Lisa', 'Sue'],
'salary': [70000, 80000, 120000, 90000]})
print(df1); print(df3);
print(pd.merge(df1, df3, left_on="employee", right_on="name"))
employee group
0 Bob Accounting
1 Jake Engineering
2 Lisa Engineering
3 Sue HR
name salary
0 Bob 70000
1 Jake 80000
2 Lisa 120000
3 Sue 90000
employee group name salary
0 Bob Accounting Bob 70000
1 Jake Engineering Jake 80000
2 Lisa Engineering Lisa 120000
3 Sue HR Sue 90000
获取的结果中会有一个多余的列, 可以通过 DataFrame 的 drop()
方法将这列去掉:
pd.merge(df1, df3, left_on="employee", right_on="name").drop('name', axis=1)
| employee | group | salary | |
|---|---|---|---|
| 0 | Bob | Accounting | 70000 |
| 1 | Jake | Engineering | 80000 |
| 2 | Lisa | Engineering | 120000 |
| 3 | Sue | HR | 90000 |
- left_index与right_index参数
除了合并列之外, 还可以合并索引。
df1a = df1.set_index('employee')
df2a = df2.set_index('employee')
print(df1a); print(df2a)
group
employee
Bob Accounting
Jake Engineering
Lisa Engineering
Sue HR
hire_date
employee
Lisa 2004
Bob 2008
Jake 2012
Sue 2014
可以通过设置 pd.merge() 中的 left_index 和 / 或
right_index 参数将索引设置为键来实现合并:
print(df1a); print(df2a);
print(pd.merge(df1a, df2a, left_index=True, right_index=True))
group
employee
Bob Accounting
Jake Engineering
Lisa Engineering
Sue HR
hire_date
employee
Lisa 2004
Bob 2008
Jake 2012
Sue 2014
group hire_date
employee
Bob Accounting 2008
Jake Engineering 2012
Lisa Engineering 2004
Sue HR 2014
可以通过设置 pd.merge() 中的 left_index 和 / 或
right_index 参数将索引设置为键来实现合并:
print(df1a); print(df2a);
print(pd.merge(df1a, df2a, left_index=True, right_index=True))
group
employee
Bob Accounting
Jake Engineering
Lisa Engineering
Sue HR
hire_date
employee
Lisa 2004
Bob 2008
Jake 2012
Sue 2014
group hire_date
employee
Bob Accounting 2008
Jake Engineering 2012
Lisa Engineering 2004
Sue HR 2014
DataFrame 实现了 join() 方法, 它可以按照索引
进行数据合并:
print(df1a); print(df2a); print(df1a.join(df2a))
group
employee
Bob Accounting
Jake Engineering
Lisa Engineering
Sue HR
hire_date
employee
Lisa 2004
Bob 2008
Jake 2012
Sue 2014
group hire_date
employee
Bob Accounting 2008
Jake Engineering 2012
Lisa Engineering 2004
Sue HR 2014
如果想将索引与列混合使用, 那么可以通过结合 left_index 与
right_on, 或者结合 left_on 与 right_index 来实现:
print(df1a); print(df3);
print(pd.merge(df1a, df3, left_index=True, right_on='name'))
group
employee
Bob Accounting
Jake Engineering
Lisa Engineering
Sue HR
name salary
0 Bob 70000
1 Jake 80000
2 Lisa 120000
3 Sue 90000
group name salary
0 Accounting Bob 70000
1 Engineering Jake 80000
2 Engineering Lisa 120000
3 HR Sue 90000
设置数据连接的集合操作规则
当一个值出现在一列, 却没有出现在另一列时, 就需要考虑集合操
作规则了。 来看看下面的例子:
df6 = pd.DataFrame({'name': ['Peter', 'Paul', 'Mary'],
'food': ['fish', 'beans', 'bread']},
columns=['name', 'food'])
df7 = pd.DataFrame({'name': ['Mary', 'Joseph'],
'drink': ['wine', 'beer']},
columns=['name', 'drink'])
print(df6); print(df7); print(pd.merge(df6, df7))
name food
0 Peter fish
1 Paul beans
2 Mary bread
name drink
0 Mary wine
1 Joseph beer
name food drink
0 Mary bread wine
在“name”列中只有一个共同的值: Mary。 默认
情况下, 结果中只会包含两个输入集合的交集, 这种连接方式被称为内
连接 。 我们可以用 how 参数设置连接方式, 默认值为
‘inner’:
pd.merge(df6, df7, how='inner')
| name | food | drink | |
|---|---|---|---|
| 0 | Mary | bread | wine |
how 参数支持的数据连接方式还有 ‘outer’、 ‘left’ 和 ‘right’。 外
连接 返回两个输入列的交集, 所有缺失值都用 NaN 填充:
print(df6); print(df7); print(pd.merge(df6, df7, how='outer'))
name food
0 Peter fish
1 Paul beans
2 Mary bread
name drink
0 Mary wine
1 Joseph beer
name food drink
0 Peter fish NaN
1 Paul beans NaN
2 Mary bread wine
3 Joseph NaN beer
左连接(left join) 和右连接(right join) 返回的结果分别只包含左列和
右列, 如下所示:
现在输出的行中只包含左边输入列的值。 如果用 how=‘right’ 的话,
输出的行则只包含右边输入列的值。
重复列名: suffixes参数
遇到两个输入 DataFrame 有重名列的情况, 来看看下
面的例子:
df8 = pd.DataFrame({'name': ['Bob', 'Jake', 'Lisa', 'Sue'],
'rank': [1, 2, 3, 4]})
df9 = pd.DataFrame({'name': ['Bob', 'Jake', 'Lisa', 'Sue'],
'rank': [3, 1, 4, 2]})
print(df8); print(df9); print(pd.merge(df8, df9, on="name"))
name rank
0 Bob 1
1 Jake 2
2 Lisa 3
3 Sue 4
name rank
0 Bob 3
1 Jake 1
2 Lisa 4
3 Sue 2
name rank_x rank_y
0 Bob 1 3
1 Jake 2 1
2 Lisa 3 4
3 Sue 4 2
由于输出结果中有两个重复的列名, 因此 pd.merge() 函数会自动为它
们增加后缀 _x 或 _y, 当然也可以通过 suffixes 参数自定义后缀名:
print(df8); print(df9);
print(pd.merge(df8, df9, on="name", suffixes=["_L", "_R"]))
name rank
0 Bob 1
1 Jake 2
2 Lisa 3
3 Sue 4
name rank
0 Bob 3
1 Jake 1
2 Lisa 4
3 Sue 2
name rank_L rank_R
0 Bob 1 3
1 Jake 2 1
2 Lisa 3 4
3 Sue 4 2
suffixes 参数同样适用于任何连接方式, 即使有三个及三个以上的重
复列名时也同样适用。
来源:CSDN
作者:少年吉
链接:https://blog.csdn.net/weixin_41503009/article/details/104186056