pandas(5)

左心房为你撑大大i 提交于 2020-02-06 03:41:26

《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
  1. 索引重复

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

  1. 类似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
  1. 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() 接口进行调用, 根据不同
的数据连接需求进行不同的操作。 下面将通过一些示例来演示这三种类
型, 并进一步介绍更多的细节。

  1. 一对一连接

一对一连接可能是最简单的数据合并类型了, 与 按列
合并十分相似。 如下面示例所示, 有两个包含同一所公司员工不同
信息的 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() 会默认丢弃原来的行索引, 不过也可以自定义。

  1. 多对一连接

多对一连接是指, 在需要连接的两个列中, 有一列的值有重复。 通
过多对一连接获得的结果 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”列, 里面有些值会因为
输入数据的对应关系而有所重复。

  1. 多对多连接

多对多连接是个有点儿复杂的概念, 不过也可以理解。 如果左右两
个输入的共同列都包含重复值, 那么合并的结果就是一种多对多连
接。 用一个例子来演示可能更容易理解。 来看下面的例子, 里面有
一个 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() 提供了一些参数处理这个问题。

  1. 参数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 有共同列名的时候才可以使用。

  1. 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
  1. 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 参数同样适用于任何连接方式, 即使有三个及三个以上的重
复列名时也同样适用。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!