赞
踩
Pandas 的基本特性之一就是高性能的内存式数据连接(join)和合并(merge)操作。如果你有使用数据库的经验,那么对这类操作一定很熟悉。Pandas 的主接口是pd.merge
函数,下面让我们通过一些示例来介绍它的用法。
pd.merge()
实现的功能基于关系代数
(relational algebra)的一部分。关系代数是处理关系型数据的通用理论,绝大多数数据库的可用操作都以此为理论基础。关系代数方法论的强大之处在于,它提出的若干简单操作规则经过组合就可以为任意数据集构建十分复杂的操作。借助在数据库或程序里已经高效实现的基础操作规范,你可以完成许多非常复杂的操作。
Pandas 在pd.merge()
函数与 Series 和 DataFrame 的join()
方法里实现了这些基础操作规则。下面来看看如何用这些简单的规则连接不同数据源的数据。
import pandas as pd import numpy as np class display(object): template = """<div style="float: left; padding: 10px;"> <p style='font-family:"Courier New", Courier, monospace'>{0}</p>{1} </div>""" def __init__(self, *args): self.args = args def _repr_html_(self): return '\n'.join(self.template.format(a, eval(a)._repr_html_()) for a in self.args) def __repr__(self): return '\n\n'.join(a + '\n' + repr(eval(a)) for a in self.args)
pd.merge()
函数实现了三种数据连接的类型:一对一
、多对一
和多对多
。
与之前介绍的按列合并十分相似,如下面示例所示,有两个包含同一所公司员工不同信息的 DataFrame:
df1 = pd.DataFrame({'员工': ['Bob', 'Jake', 'Lisa', 'Sue'],
'部门': ['会计', '工程', '工程', '招聘']})
df2 = pd.DataFrame({'员工': ['Lisa', 'Bob', 'Jake', 'Sue'],
'入职日期': [2004, 2008, 2012, 2014]})
display('df1', 'df2')
df1
员工 | 部门 | |
---|---|---|
0 | Bob | 会计 |
1 | Jake | 工程 |
2 | Lisa | 工程 |
3 | Sue | 招聘 |
df2
员工 | 入职日期 | |
---|---|---|
0 | Lisa | 2004 |
1 | Bob | 2008 |
2 | Jake | 2012 |
3 | Sue | 2014 |
若想将这两个 DataFrame 合并成一个 DataFrame,可以用pd.merge()
函数实现:
df3 = pd.merge(df1, df2)
df3
员工 | 部门 | 入职日期 | |
---|---|---|---|
0 | Bob | 会计 | 2008 |
1 | Jake | 工程 | 2012 |
2 | Lisa | 工程 | 2004 |
3 | Sue | 招聘 | 2014 |
pd.merge()
方法会默认丢弃原来的行索引,不过也可以自定义。
多对一连接是指,在需要连接的两个列中,有一列的值有重复。通过多对一连接获得的结果 DataFrame 将会保留重复值。请看下面的例子:
df4 = pd.DataFrame({'部门': ['会计', '工程', '招聘'],
'领导': ['Carly', 'Guido', 'Steve']})
display('df3', 'df4', 'pd.merge(df3, df4)')
df3
员工 | 部门 | 入职日期 | |
---|---|---|---|
0 | Bob | 会计 | 2008 |
1 | Jake | 工程 | 2012 |
2 | Lisa | 工程 | 2004 |
3 | Sue | 招聘 | 2014 |
df4
部门 | 领导 | |
---|---|---|
0 | 会计 | Carly |
1 | 工程 | Guido |
2 | 招聘 | Steve |
pd.merge(df3, df4)
员工 | 部门 | 入职日期 | 领导 | |
---|---|---|---|---|
0 | Bob | 会计 | 2008 | Carly |
1 | Jake | 工程 | 2012 | Guido |
2 | Lisa | 工程 | 2004 | Guido |
3 | Sue | 招聘 | 2014 | Steve |
在结果 DataFrame 中多了一个“领导”列,里面会因为输入数据的对应关系而有所重复。
多对多连接是个有点复杂的概念,不过也可以理解。如果左右两个输入的共同列都包含重复值,那么合并的结果就是一种多对多连接。用一个例子来演示更容易理解。来看下面的例子,里面有一个 DataFrame 显示不同岗位员工的一种或多种能力。
通过多对多连接,就可以得知每位员工所具备的能力:
df5 = pd.DataFrame({'部门': ['会计', '会计', '工程', '工程', '招聘', '招聘'],
'技能': ['数学', '制表', '编码', 'Linux', '制表', '管理']})
display('df1', 'df5', "pd.merge(df1, df5)")
df1
员工 | 部门 | |
---|---|---|
0 | Bob | 会计 |
1 | Jake | 工程 |
2 | Lisa | 工程 |
3 | Sue | 招聘 |
df5
部门 | 技能 | |
---|---|---|
0 | 会计 | 数学 |
1 | 会计 | 制表 |
2 | 工程 | 编码 |
3 | 工程 | Linux |
4 | 招聘 | 制表 |
5 | 招聘 | 管理 |
pd.merge(df1, df5)
员工 | 部门 | 技能 | |
---|---|---|---|
0 | Bob | 会计 | 数学 |
1 | Bob | 会计 | 制表 |
2 | Jake | 工程 | 编码 |
3 | Jake | 工程 | Linux |
4 | Lisa | 工程 | 编码 |
5 | Lisa | 工程 | Linux |
6 | Sue | 招聘 | 制表 |
7 | Sue | 招聘 | 管理 |
这三种数据连接可以直接与其他 Pandas 工具组合使用,从而实现各种各样的功能。但是工作中的真实数据集往往不像示例中演示的那么干净、整洁。下面就来介绍pd.merge()
的一些功能,它们可以让你更好的应对数据连接中的问题。
我们已经知道pd.merge()
的默认行为:它会将两个输入的一个或多个共同列作为键进行合并。但是由于两个输入要合并的列通常都不是同名的,因此pd.merge()
提供了一些参数处理这个问题。
on
的用法最简单的方法就是直接将参数on
设置为一个列名称或者一个包含多列名称的列表:
display('df1', 'df2', "pd.merge(df1, df2, on='员工')")
df1
员工 | 部门 | |
---|---|---|
0 | Bob | 会计 |
1 | Jake | 工程 |
2 | Lisa | 工程 |
3 | Sue | 招聘 |
df2
员工 | 入职日期 | |
---|---|---|
0 | Lisa | 2004 |
1 | Bob | 2008 |
2 | Jake | 2012 |
3 | Sue | 2014 |
pd.merge(df1, df2, on='员工')
员工 | 部门 | 入职日期 | |
---|---|---|---|
0 | Bob | 会计 | 2008 |
1 | Jake | 工程 | 2012 |
2 | Lisa | 工程 | 2004 |
3 | Sue | 招聘 | 2014 |
left_on
与right_on
参数有时候你需要合并连个列名不同的数据集,例如前面的员工信息表中有一个字段不是“员工”
而是“姓名”
。在这种情况下,就可以用left_on
与right_on
参数来指定列名:
df3 = pd.DataFrame({'姓名': ['Bob', 'Jake', 'Lisa', 'Sue'],
'工资': [70000, 80000, 120000, 90000]})
display('df1', 'df3', 'pd.merge(df1, df3, left_on="员工", right_on="姓名")')
df1
员工 | 部门 | |
---|---|---|
0 | Bob | 会计 |
1 | Jake | 工程 |
2 | Lisa | 工程 |
3 | Sue | 招聘 |
df3
姓名 | 工资 | |
---|---|---|
0 | Bob | 70000 |
1 | Jake | 80000 |
2 | Lisa | 120000 |
3 | Sue | 90000 |
pd.merge(df1, df3, left_on="员工", right_on="姓名")
员工 | 部门 | 姓名 | 工资 | |
---|---|---|---|---|
0 | Bob | 会计 | Bob | 70000 |
1 | Jake | 工程 | Jake | 80000 |
2 | Lisa | 工程 | Lisa | 120000 |
3 | Sue | 招聘 | Sue | 90000 |
但是获取的结果中有一个多余的列,可以通过 DataFrame 的drop()
方法将这一列去掉:
pd.merge(df1, df3, left_on="员工", right_on="姓名").drop("姓名", axis=1)
员工 | 部门 | 工资 | |
---|---|---|---|
0 | Bob | 会计 | 70000 |
1 | Jake | 工程 | 80000 |
2 | Lisa | 工程 | 120000 |
3 | Sue | 招聘 | 90000 |
left_index
与right_index
参数除了合并列之外,你可能还需要合并索引:
df1a = df1.set_index('员工')
df2a = df2.set_index('员工')
display('df1a', 'df2a')
df1a
部门 | |
---|---|
员工 | |
Bob | 会计 |
Jake | 工程 |
Lisa | 工程 |
Sue | 招聘 |
df2a
入职日期 | |
---|---|
员工 | |
Lisa | 2004 |
Bob | 2008 |
Jake | 2012 |
Sue | 2014 |
# pd.merge(df1a, df2a) 这样合并会报错,报错如下(MergeError:没有要执行合并的通用列)
# MergeError: No common columns to perform merge on. Merge options: left_on=None, right_on=None, left_index=False, right_index=False
display('df1a', 'df2a', "pd.merge(df1a, df2a, left_index=True, right_index=True)")
df1a
部门 | |
---|---|
员工 | |
Bob | 会计 |
Jake | 工程 |
Lisa | 工程 |
Sue | 招聘 |
df2a
入职日期 | |
---|---|
员工 | |
Lisa | 2004 |
Bob | 2008 |
Jake | 2012 |
Sue | 2014 |
pd.merge(df1a, df2a, left_index=True, right_index=True)
部门 | 入职日期 | |
---|---|---|
员工 | ||
Bob | 会计 | 2008 |
Jake | 工程 | 2012 |
Lisa | 工程 | 2004 |
Sue | 招聘 | 2014 |
为了方便考虑,DataFrame 实现了join()
方法,它可以按照索引进行数据合并:
display('df1a', 'df2a', 'df1a.join(df2a)')
df1a
部门 | |
---|---|
员工 | |
Bob | 会计 |
Jake | 工程 |
Lisa | 工程 |
Sue | 招聘 |
df2a
入职日期 | |
---|---|
员工 | |
Lisa | 2004 |
Bob | 2008 |
Jake | 2012 |
Sue | 2014 |
df1a.join(df2a)
部门 | 入职日期 | |
---|---|---|
员工 | ||
Bob | 会计 | 2008 |
Jake | 工程 | 2012 |
Lisa | 工程 | 2004 |
Sue | 招聘 | 2014 |
如果想将索引与列混合使用,那么可以通过结合left_index
与right_on
,或者结合left_on
与right_index
来实现:
# df1a 的 索引,与 df3 的 “姓名” 列,数据相同
display('df1a', 'df3', "pd.merge(df1a, df3, left_index=True, right_on='姓名')")
df1a
部门 | |
---|---|
员工 | |
Bob | 会计 |
Jake | 工程 |
Lisa | 工程 |
Sue | 招聘 |
df3
姓名 | 工资 | |
---|---|---|
0 | Bob | 70000 |
1 | Jake | 80000 |
2 | Lisa | 120000 |
3 | Sue | 90000 |
pd.merge(df1a, df3, left_index=True, right_on='姓名')
部门 | 姓名 | 工资 | |
---|---|---|---|
0 | 会计 | Bob | 70000 |
1 | 工程 | Jake | 80000 |
2 | 工程 | Lisa | 120000 |
3 | 招聘 | Sue | 90000 |
通过前面的演示,我们总结出数据连接的一个重要条件:集合操作规则。
df6 = pd.DataFrame({'姓名': ['Peter', 'Paul', 'Mary'],
'食物': ['鱼', '大豆', '面包']},
columns=['姓名', '食物'])
df7 = pd.DataFrame({'姓名': ['Mary', 'Joseph'],
'饮品': ['葡萄酒', '啤酒']},
columns=['姓名', '饮品'])
display('df6', 'df7', 'pd.merge(df6, df7)')
df6
姓名 | 食物 | |
---|---|---|
0 | Peter | 鱼 |
1 | Paul | 大豆 |
2 | Mary | 面包 |
df7
姓名 | 饮品 | |
---|---|---|
0 | Mary | 葡萄酒 |
1 | Joseph | 啤酒 |
pd.merge(df6, df7)
姓名 | 食物 | 饮品 | |
---|---|---|---|
0 | Mary | 面包 | 葡萄酒 |
我们合并两个数据集,在“姓名”列中只有一个共同值:Mary。默认情况下,结果中只会包含两个输入集合的交集
,这种连接方式叫做内连接
(inner join)。我们可以用how
参数设置连接方式,默认值是'inner'
:
pd.merge(df6, df7, how='inner')
姓名 | 食物 | 饮品 | |
---|---|---|---|
0 | Mary | 面包 | 葡萄酒 |
how
参数支持的数据连接方式还有'outer'
、'left'
和'right'
。外连接
(outer join)返回两个输入列的并集,所有缺失值都用NaN
填充:
display('df6', 'df7', "pd.merge(df6, df7, how='outer')")
df6
姓名 | 食物 | |
---|---|---|
0 | Peter | 鱼 |
1 | Paul | 大豆 |
2 | Mary | 面包 |
df7
姓名 | 饮品 | |
---|---|---|
0 | Mary | 葡萄酒 |
1 | Joseph | 啤酒 |
pd.merge(df6, df7, how='outer')
姓名 | 食物 | 饮品 | |
---|---|---|---|
0 | Peter | 鱼 | NaN |
1 | Paul | 大豆 | NaN |
2 | Mary | 面包 | 葡萄酒 |
3 | Joseph | NaN | 啤酒 |
左连接
(left join)和右连接
(right join)返回的结果分别只包含左列和右列:
display('df6', 'df7', "pd.merge(df6, df7, how='left')")
df6
姓名 | 食物 | |
---|---|---|
0 | Peter | 鱼 |
1 | Paul | 大豆 |
2 | Mary | 面包 |
df7
姓名 | 饮品 | |
---|---|---|
0 | Mary | 葡萄酒 |
1 | Joseph | 啤酒 |
pd.merge(df6, df7, how='left')
姓名 | 食物 | 饮品 | |
---|---|---|---|
0 | Peter | 鱼 | NaN |
1 | Paul | 大豆 | NaN |
2 | Mary | 面包 | 葡萄酒 |
display('df6', 'df7', "pd.merge(df6, df7, how='right')")
df6
姓名 | 食物 | |
---|---|---|
0 | Peter | 鱼 |
1 | Paul | 大豆 |
2 | Mary | 面包 |
df7
姓名 | 饮品 | |
---|---|---|
0 | Mary | 葡萄酒 |
1 | Joseph | 啤酒 |
pd.merge(df6, df7, how='right')
姓名 | 食物 | 饮品 | |
---|---|---|---|
0 | Mary | 面包 | 葡萄酒 |
1 | Joseph | NaN | 啤酒 |
suffixes
参数df8 = pd.DataFrame({'姓名': ['Bob', 'Jake', 'Lisa', 'Sue'],
'等级': [1, 2, 3, 4]})
df9 = pd.DataFrame({'姓名': ['Bob', 'Jake', 'Lisa', 'Sue'],
'等级': [3, 1, 4, 2]})
display('df8', 'df9', 'pd.merge(df8, df9, on="姓名")')
df8
姓名 | 等级 | |
---|---|---|
0 | Bob | 1 |
1 | Jake | 2 |
2 | Lisa | 3 |
3 | Sue | 4 |
df9
姓名 | 等级 | |
---|---|---|
0 | Bob | 3 |
1 | Jake | 1 |
2 | Lisa | 4 |
3 | Sue | 2 |
pd.merge(df8, df9, on="姓名")
姓名 | 等级_x | 等级_y | |
---|---|---|---|
0 | Bob | 1 | 3 |
1 | Jake | 2 | 1 |
2 | Lisa | 3 | 4 |
3 | Sue | 4 | 2 |
由于输出结果中有两个重复的列名,因此pd.merge()
函数会自动为它们增加后缀_x
或_y
,当然也可以用通过suffixes
参数自定义后缀:
display('df8', 'df9', 'pd.merge(df8, df9, on="姓名", suffixes=["_L", "_R"])')
df8
姓名 | 等级 | |
---|---|---|
0 | Bob | 1 |
1 | Jake | 2 |
2 | Lisa | 3 |
3 | Sue | 4 |
df9
姓名 | 等级 | |
---|---|---|
0 | Bob | 3 |
1 | Jake | 1 |
2 | Lisa | 4 |
3 | Sue | 2 |
pd.merge(df8, df9, on="姓名", suffixes=["_L", "_R"])
姓名 | 等级_L | 等级_R | |
---|---|---|---|
0 | Bob | 1 | 3 |
1 | Jake | 2 | 1 |
2 | Lisa | 3 | 4 |
3 | Sue | 4 | 2 |
suffixes
参数同样适用于任何连接方式,即使有三个及三个以上的重复列名时也同样适用。
pop = pd.read_csv('data/state-population.csv') # 人口
areas = pd.read_csv('data/state-areas.csv') # 地区
abbrevs = pd.read_csv('data/state-abbrevs.csv') # 地区名称缩写
display('pop.head()', 'areas.head()', 'abbrevs.head()')
pop.head()
state/region | ages | year | population | |
---|---|---|---|---|
0 | AL | under18 | 2012 | 1117489.0 |
1 | AL | total | 2012 | 4817528.0 |
2 | AL | under18 | 2010 | 1130966.0 |
3 | AL | total | 2010 | 4785570.0 |
4 | AL | under18 | 2011 | 1125763.0 |
areas.head()
state | area (sq. mi) | |
---|---|---|
0 | Alabama | 52423 |
1 | Alaska | 656425 |
2 | Arizona | 114006 |
3 | Arkansas | 53182 |
4 | California | 163707 |
abbrevs.head()
state | abbreviation | |
---|---|---|
0 | Alabama | AL |
1 | Alaska | AK |
2 | Arizona | AZ |
3 | Arkansas | AR |
4 | California | CA |
有了数据,我们先完成一个比较简单的小指标:美国各州的人口密度排名:
merged = pd.merge(pop, abbrevs, how='outer', left_on='state/region', right_on="abbreviation")
merged = merged.drop("abbreviation", axis=1)
merged.head()
state/region | ages | year | population | state | |
---|---|---|---|---|---|
0 | AL | under18 | 2012 | 1117489.0 | Alabama |
1 | AL | total | 2012 | 4817528.0 | Alabama |
2 | AL | under18 | 2010 | 1130966.0 | Alabama |
3 | AL | total | 2010 | 4785570.0 | Alabama |
4 | AL | under18 | 2011 | 1125763.0 | Alabama |
merged.isnull().any()
state/region False
ages False
year False
population True
state True
dtype: bool
部分population
与state
有缺失值,让我们仔细看看那些数据!
merged[merged['population'].isnull()].head()
state/region | ages | year | population | state | |
---|---|---|---|---|---|
2448 | PR | under18 | 1990 | NaN | NaN |
2449 | PR | total | 1990 | NaN | NaN |
2450 | PR | total | 1991 | NaN | NaN |
2451 | PR | under18 | 1991 | NaN | NaN |
2452 | PR | total | 1993 | NaN | NaN |
然后我们看下有哪些州缩写没有数据:
merged.loc[merged['state'].isnull(), 'state/region'].unique()
array(['PR', 'USA'], dtype=object)
上面可以看到:人口数据中包含PR
和USA
,但是这两项没有出现在州名称缩写表中。
merged.loc[merged['state/region'] == 'PR', 'state'] = 'Puerto Rico'
merged.loc[merged['state/region'] == 'USA', 'state'] = 'United States'
merged.isnull().any()
state/region False
ages False
year False
population True
state False
dtype: bool
final = pd.merge(merged, areas, on='state', how='left')
final.head()
state/region | ages | year | population | state | area (sq. mi) | |
---|---|---|---|---|---|---|
0 | AL | under18 | 2012 | 1117489.0 | Alabama | 52423.0 |
1 | AL | total | 2012 | 4817528.0 | Alabama | 52423.0 |
2 | AL | under18 | 2010 | 1130966.0 | Alabama | 52423.0 |
3 | AL | total | 2010 | 4785570.0 | Alabama | 52423.0 |
4 | AL | under18 | 2011 | 1125763.0 | Alabama | 52423.0 |
# 检查 缺失值
final.isnull().any()
state/region False
ages False
year False
population True
state False
area (sq. mi) True
dtype: bool
# 查看 缺失值
# final.loc[final['area (sq. mi)'].isnull(), 'state'].unique() # 和下面同效
final[final['area (sq. mi)'].isnull()]['state'].unique()
array(['United States'], dtype=object)
上述缺失值是全国面积数据
,针对本案例非必要,可以删除:
# 删除 缺失值
final.dropna(inplace=True)
final.head()
state/region | ages | year | population | state | area (sq. mi) | |
---|---|---|---|---|---|---|
0 | AL | under18 | 2012 | 1117489.0 | Alabama | 52423.0 |
1 | AL | total | 2012 | 4817528.0 | Alabama | 52423.0 |
2 | AL | under18 | 2010 | 1130966.0 | Alabama | 52423.0 |
3 | AL | total | 2010 | 4785570.0 | Alabama | 52423.0 |
4 | AL | under18 | 2011 | 1125763.0 | Alabama | 52423.0 |
现在数据准备好了。
# data2010 = final.query("year == 2010 & ages == 'total'") # and 替换成 & 也可以
data2010 = final.query("year == 2010 and ages == 'total'")
data2010.head()
state/region | ages | year | population | state | area (sq. mi) | |
---|---|---|---|---|---|---|
3 | AL | total | 2010 | 4785570.0 | Alabama | 52423.0 |
91 | AK | total | 2010 | 713868.0 | Alaska | 656425.0 |
101 | AZ | total | 2010 | 6408790.0 | Arizona | 114006.0 |
189 | AR | total | 2010 | 2922280.0 | Arkansas | 53182.0 |
197 | CA | total | 2010 | 37333601.0 | California | 163707.0 |
优先对索引进行重置,然后在计算:
data2010.set_index('state', inplace=True)
density = data2010['population'] / data2010['area (sq. mi)']
density.head()
state
Alabama 91.287603
Alaska 1.087509
Arizona 56.214497
Arkansas 54.948667
California 228.051342
dtype: float64
# 排序
density.sort_values(ascending=False, inplace=True)
density.head()
state
District of Columbia 8898.897059
Puerto Rico 1058.665149
New Jersey 1009.253268
Rhode Island 681.339159
Connecticut 645.600649
dtype: float64
# 查看最后几名
density.tail()
state
South Dakota 10.583512
North Dakota 9.537565
Montana 6.736171
Wyoming 5.768079
Alaska 1.087509
dtype: float64
在对较大的数据进行分析时,一项基本的工作就是有效的数据累计(summarization):计算累计(aggregation)指标,如sum()
、mean()
、median()
、min()
和max()
,其中每一个指标都呈现了大数据集的特征。在这一节中,我们将探索 Pandas 的累计功能,从类似前面 NumPy 数组中的简单操作,到基于groupby
实现的复杂操作。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。