当前位置:   article > 正文

【Python数据科学】第三章 Pandas 数据处理(下半章)_merged.loc

merged.loc

7、合并数据集:合并与连接

Pandas 的基本特性之一就是高性能的内存式数据连接(join)和合并(merge)操作。如果你有使用数据库的经验,那么对这类操作一定很熟悉。Pandas 的主接口是pd.merge函数,下面让我们通过一些示例来介绍它的用法。

7.1、关系代数

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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

7.2、数据连接的类型

pd.merge()函数实现了三种数据连接的类型:一对一多对一多对多

7.2.1、一对一连接

与之前介绍的按列合并十分相似,如下面示例所示,有两个包含同一所公司员工不同信息的 DataFrame:

df1 = pd.DataFrame({'员工': ['Bob', 'Jake', 'Lisa', 'Sue'],
                    '部门': ['会计', '工程', '工程', '招聘']})
df2 = pd.DataFrame({'员工': ['Lisa', 'Bob', 'Jake', 'Sue'],
                    '入职日期': [2004, 2008, 2012, 2014]})
display('df1', 'df2')
  • 1
  • 2
  • 3
  • 4
  • 5

df1

员工部门
0Bob会计
1Jake工程
2Lisa工程
3Sue招聘

df2

员工入职日期
0Lisa2004
1Bob2008
2Jake2012
3Sue2014

若想将这两个 DataFrame 合并成一个 DataFrame,可以用pd.merge()函数实现:

df3 = pd.merge(df1, df2)
df3
  • 1
  • 2
员工部门入职日期
0Bob会计2008
1Jake工程2012
2Lisa工程2004
3Sue招聘2014

pd.merge()方法会默认丢弃原来的行索引,不过也可以自定义。

7.2.2、多对一连接

多对一连接是指,在需要连接的两个列中,有一列的值有重复。通过多对一连接获得的结果 DataFrame 将会保留重复值。请看下面的例子:

df4 = pd.DataFrame({'部门': ['会计', '工程', '招聘'],
                    '领导': ['Carly', 'Guido', 'Steve']})
display('df3', 'df4', 'pd.merge(df3, df4)')
  • 1
  • 2
  • 3

df3

员工部门入职日期
0Bob会计2008
1Jake工程2012
2Lisa工程2004
3Sue招聘2014

df4

部门领导
0会计Carly
1工程Guido
2招聘Steve

pd.merge(df3, df4)

员工部门入职日期领导
0Bob会计2008Carly
1Jake工程2012Guido
2Lisa工程2004Guido
3Sue招聘2014Steve

在结果 DataFrame 中多了一个“领导”列,里面会因为输入数据的对应关系而有所重复。

7.2.3、多对多连接

多对多连接是个有点复杂的概念,不过也可以理解。如果左右两个输入的共同列都包含重复值,那么合并的结果就是一种多对多连接。用一个例子来演示更容易理解。来看下面的例子,里面有一个 DataFrame 显示不同岗位员工的一种或多种能力。

通过多对多连接,就可以得知每位员工所具备的能力:

df5 = pd.DataFrame({'部门': ['会计', '会计', '工程', '工程', '招聘', '招聘'],
                    '技能': ['数学', '制表', '编码', 'Linux', '制表', '管理']})
display('df1', 'df5', "pd.merge(df1, df5)")
  • 1
  • 2
  • 3

df1

员工部门
0Bob会计
1Jake工程
2Lisa工程
3Sue招聘

df5

部门技能
0会计数学
1会计制表
2工程编码
3工程Linux
4招聘制表
5招聘管理

pd.merge(df1, df5)

员工部门技能
0Bob会计数学
1Bob会计制表
2Jake工程编码
3Jake工程Linux
4Lisa工程编码
5Lisa工程Linux
6Sue招聘制表
7Sue招聘管理

这三种数据连接可以直接与其他 Pandas 工具组合使用,从而实现各种各样的功能。但是工作中的真实数据集往往不像示例中演示的那么干净、整洁。下面就来介绍pd.merge()的一些功能,它们可以让你更好的应对数据连接中的问题。

7.3、设置数据合并的键

我们已经知道pd.merge()的默认行为:它会将两个输入的一个或多个共同列作为键进行合并。但是由于两个输入要合并的列通常都不是同名的,因此pd.merge()提供了一些参数处理这个问题。

7.3.1、参数on的用法

最简单的方法就是直接将参数on设置为一个列名称或者一个包含多列名称的列表:

  • 这个参数只有两个 DataFrame 有共同列名的时候才可以使用。
display('df1', 'df2', "pd.merge(df1, df2, on='员工')")
  • 1

df1

员工部门
0Bob会计
1Jake工程
2Lisa工程
3Sue招聘

df2

员工入职日期
0Lisa2004
1Bob2008
2Jake2012
3Sue2014

pd.merge(df1, df2, on='员工')

员工部门入职日期
0Bob会计2008
1Jake工程2012
2Lisa工程2004
3Sue招聘2014

7.3.2、left_onright_on参数

有时候你需要合并连个列名不同的数据集,例如前面的员工信息表中有一个字段不是“员工”而是“姓名”。在这种情况下,就可以用left_onright_on参数来指定列名:

df3 = pd.DataFrame({'姓名': ['Bob', 'Jake', 'Lisa', 'Sue'],
                    '工资': [70000, 80000, 120000, 90000]})
display('df1', 'df3', 'pd.merge(df1, df3, left_on="员工", right_on="姓名")')
  • 1
  • 2
  • 3

df1

员工部门
0Bob会计
1Jake工程
2Lisa工程
3Sue招聘

df3

姓名工资
0Bob70000
1Jake80000
2Lisa120000
3Sue90000

pd.merge(df1, df3, left_on="员工", right_on="姓名")

员工部门姓名工资
0Bob会计Bob70000
1Jake工程Jake80000
2Lisa工程Lisa120000
3Sue招聘Sue90000

但是获取的结果中有一个多余的列,可以通过 DataFrame 的drop()方法将这一列去掉:

pd.merge(df1, df3, left_on="员工", right_on="姓名").drop("姓名", axis=1)
  • 1
员工部门工资
0Bob会计70000
1Jake工程80000
2Lisa工程120000
3Sue招聘90000

7.3.3、left_indexright_index参数

除了合并列之外,你可能还需要合并索引:

df1a = df1.set_index('员工')
df2a = df2.set_index('员工')
display('df1a', 'df2a')
  • 1
  • 2
  • 3

df1a

部门
员工
Bob会计
Jake工程
Lisa工程
Sue招聘

df2a

入职日期
员工
Lisa2004
Bob2008
Jake2012
Sue2014
# 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)")
  • 1
  • 2
  • 3

df1a

部门
员工
Bob会计
Jake工程
Lisa工程
Sue招聘

df2a

入职日期
员工
Lisa2004
Bob2008
Jake2012
Sue2014

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)')
  • 1

df1a

部门
员工
Bob会计
Jake工程
Lisa工程
Sue招聘

df2a

入职日期
员工
Lisa2004
Bob2008
Jake2012
Sue2014

df1a.join(df2a)

部门入职日期
员工
Bob会计2008
Jake工程2012
Lisa工程2004
Sue招聘2014

如果想将索引与列混合使用,那么可以通过结合left_indexright_on,或者结合left_onright_index来实现:

# df1a 的 索引,与 df3 的 “姓名” 列,数据相同
display('df1a', 'df3', "pd.merge(df1a, df3, left_index=True, right_on='姓名')")
  • 1
  • 2

df1a

部门
员工
Bob会计
Jake工程
Lisa工程
Sue招聘

df3

姓名工资
0Bob70000
1Jake80000
2Lisa120000
3Sue90000

pd.merge(df1a, df3, left_index=True, right_on='姓名')

部门姓名工资
0会计Bob70000
1工程Jake80000
2工程Lisa120000
3招聘Sue90000

7.4、设置数据连接的集合操作规则

通过前面的演示,我们总结出数据连接的一个重要条件:集合操作规则。

  • 当一个值出现在一列,却没有出现在另一列时,就需要考虑集合操作规则了。
df6 = pd.DataFrame({'姓名': ['Peter', 'Paul', 'Mary'],
                    '食物': ['鱼', '大豆', '面包']},
                   columns=['姓名', '食物'])
df7 = pd.DataFrame({'姓名': ['Mary', 'Joseph'],
                    '饮品': ['葡萄酒', '啤酒']},
                   columns=['姓名', '饮品'])
display('df6', 'df7', 'pd.merge(df6, df7)')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

df6

姓名食物
0Peter
1Paul大豆
2Mary面包

df7

姓名饮品
0Mary葡萄酒
1Joseph啤酒

pd.merge(df6, df7)

姓名食物饮品
0Mary面包葡萄酒

我们合并两个数据集,在“姓名”列中只有一个共同值:Mary。默认情况下,结果中只会包含两个输入集合的交集,这种连接方式叫做内连接(inner join)。我们可以用how参数设置连接方式,默认值是'inner'

pd.merge(df6, df7, how='inner')
  • 1
姓名食物饮品
0Mary面包葡萄酒

how参数支持的数据连接方式还有'outer''left''right'外连接(outer join)返回两个输入列的并集,所有缺失值都用NaN填充:

display('df6', 'df7', "pd.merge(df6, df7, how='outer')")
  • 1

df6

姓名食物
0Peter
1Paul大豆
2Mary面包

df7

姓名饮品
0Mary葡萄酒
1Joseph啤酒

pd.merge(df6, df7, how='outer')

姓名食物饮品
0PeterNaN
1Paul大豆NaN
2Mary面包葡萄酒
3JosephNaN啤酒

左连接(left join)和右连接(right join)返回的结果分别只包含左列和右列:

display('df6', 'df7', "pd.merge(df6, df7, how='left')")
  • 1

df6

姓名食物
0Peter
1Paul大豆
2Mary面包

df7

姓名饮品
0Mary葡萄酒
1Joseph啤酒

pd.merge(df6, df7, how='left')

姓名食物饮品
0PeterNaN
1Paul大豆NaN
2Mary面包葡萄酒
display('df6', 'df7', "pd.merge(df6, df7, how='right')")
  • 1

df6

姓名食物
0Peter
1Paul大豆
2Mary面包

df7

姓名饮品
0Mary葡萄酒
1Joseph啤酒

pd.merge(df6, df7, how='right')

姓名食物饮品
0Mary面包葡萄酒
1JosephNaN啤酒

7.5、重复列名: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="姓名")')
  • 1
  • 2
  • 3
  • 4
  • 5

df8

姓名等级
0Bob1
1Jake2
2Lisa3
3Sue4

df9

姓名等级
0Bob3
1Jake1
2Lisa4
3Sue2

pd.merge(df8, df9, on="姓名")

姓名等级_x等级_y
0Bob13
1Jake21
2Lisa34
3Sue42

由于输出结果中有两个重复的列名,因此pd.merge()函数会自动为它们增加后缀_x_y,当然也可以用通过suffixes参数自定义后缀:

display('df8', 'df9', 'pd.merge(df8, df9, on="姓名", suffixes=["_L", "_R"])')
  • 1

df8

姓名等级
0Bob1
1Jake2
2Lisa3
3Sue4

df9

姓名等级
0Bob3
1Jake1
2Lisa4
3Sue2

pd.merge(df8, df9, on="姓名", suffixes=["_L", "_R"])

姓名等级_L等级_R
0Bob13
1Jake21
2Lisa34
3Sue42

suffixes参数同样适用于任何连接方式,即使有三个及三个以上的重复列名时也同样适用。

7.6、案例:美国各州的统计数据

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()')
  • 1
  • 2
  • 3
  • 4
  • 5

pop.head()

state/regionagesyearpopulation
0ALunder1820121117489.0
1ALtotal20124817528.0
2ALunder1820101130966.0
3ALtotal20104785570.0
4ALunder1820111125763.0

areas.head()

statearea (sq. mi)
0Alabama52423
1Alaska656425
2Arizona114006
3Arkansas53182
4California163707

abbrevs.head()

stateabbreviation
0AlabamaAL
1AlaskaAK
2ArizonaAZ
3ArkansasAR
4CaliforniaCA

有了数据,我们先完成一个比较简单的小指标:美国各州的人口密度排名:

7.6.1、先合并人口和州缩写数据

merged = pd.merge(pop, abbrevs, how='outer', left_on='state/region', right_on="abbreviation")
merged = merged.drop("abbreviation", axis=1)
merged.head()
  • 1
  • 2
  • 3
state/regionagesyearpopulationstate
0ALunder1820121117489.0Alabama
1ALtotal20124817528.0Alabama
2ALunder1820101130966.0Alabama
3ALtotal20104785570.0Alabama
4ALunder1820111125763.0Alabama

7.6.2、检查是否有数据缺失

merged.isnull().any()
  • 1
state/region    False
ages            False
year            False
population       True
state            True
dtype: bool
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

部分populationstate有缺失值,让我们仔细看看那些数据!

merged[merged['population'].isnull()].head()
  • 1
state/regionagesyearpopulationstate
2448PRunder181990NaNNaN
2449PRtotal1990NaNNaN
2450PRtotal1991NaNNaN
2451PRunder181991NaNNaN
2452PRtotal1993NaNNaN

然后我们看下有哪些州缩写没有数据:

merged.loc[merged['state'].isnull(), 'state/region'].unique()
  • 1
array(['PR', 'USA'], dtype=object)
  • 1

上面可以看到:人口数据中包含PRUSA,但是这两项没有出现在州名称缩写表中。

7.6.3、填充缺失值

merged.loc[merged['state/region'] == 'PR', 'state'] = 'Puerto Rico'
merged.loc[merged['state/region'] == 'USA', 'state'] = 'United States'
merged.isnull().any()
  • 1
  • 2
  • 3
state/region    False
ages            False
year            False
population       True
state           False
dtype: bool
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

7.6.4、合入面积数据

final = pd.merge(merged, areas, on='state', how='left')
final.head()
  • 1
  • 2
state/regionagesyearpopulationstatearea (sq. mi)
0ALunder1820121117489.0Alabama52423.0
1ALtotal20124817528.0Alabama52423.0
2ALunder1820101130966.0Alabama52423.0
3ALtotal20104785570.0Alabama52423.0
4ALunder1820111125763.0Alabama52423.0
# 检查 缺失值
final.isnull().any()
  • 1
  • 2
state/region     False
ages             False
year             False
population        True
state            False
area (sq. mi)     True
dtype: bool
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
# 查看 缺失值
# final.loc[final['area (sq. mi)'].isnull(), 'state'].unique()  # 和下面同效
final[final['area (sq. mi)'].isnull()]['state'].unique()
  • 1
  • 2
  • 3
array(['United States'], dtype=object)
  • 1

上述缺失值是全国面积数据,针对本案例非必要,可以删除:

# 删除 缺失值
final.dropna(inplace=True)
final.head()
  • 1
  • 2
  • 3
state/regionagesyearpopulationstatearea (sq. mi)
0ALunder1820121117489.0Alabama52423.0
1ALtotal20124817528.0Alabama52423.0
2ALunder1820101130966.0Alabama52423.0
3ALtotal20104785570.0Alabama52423.0
4ALunder1820111125763.0Alabama52423.0

现在数据准备好了。

7.6.5、先计算2010年的各州人口以及总人口数据

# data2010 = final.query("year == 2010 & ages == 'total'")  # and 替换成 & 也可以
data2010 = final.query("year == 2010 and ages == 'total'")
data2010.head()
  • 1
  • 2
  • 3
state/regionagesyearpopulationstatearea (sq. mi)
3ALtotal20104785570.0Alabama52423.0
91AKtotal2010713868.0Alaska656425.0
101AZtotal20106408790.0Arizona114006.0
189ARtotal20102922280.0Arkansas53182.0
197CAtotal201037333601.0California163707.0

7.6.7、计算人口密度并排序

优先对索引进行重置,然后在计算:

data2010.set_index('state', inplace=True)
density = data2010['population'] / data2010['area (sq. mi)']
density.head()
  • 1
  • 2
  • 3
state
Alabama        91.287603
Alaska          1.087509
Arizona        56.214497
Arkansas       54.948667
California    228.051342
dtype: float64
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
# 排序
density.sort_values(ascending=False, inplace=True)
density.head()
  • 1
  • 2
  • 3
state
District of Columbia    8898.897059
Puerto Rico             1058.665149
New Jersey              1009.253268
Rhode Island             681.339159
Connecticut              645.600649
dtype: float64
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
# 查看最后几名
density.tail()
  • 1
  • 2
state
South Dakota    10.583512
North Dakota     9.537565
Montana          6.736171
Wyoming          5.768079
Alaska           1.087509
dtype: float64
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

8、累计与分组

在对较大的数据进行分析时,一项基本的工作就是有效的数据累计(summarization):计算累计(aggregation)指标,如sum()mean()median()min()max(),其中每一个指标都呈现了大数据集的特征。在这一节中,我们将探索 Pandas 的累计功能,从类似前面 NumPy 数组中的简单操作,到基于groupby实现的复杂操作。

8.1、行星数据
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/运维做开发/article/detail/790310

推荐阅读
相关标签