将两个表或者多个表关联在一起是常见的运算,这时通常使用 SQL join 的方式进行关联并进行后续计算。但有时数据并不存储在数据库,而是以文件的形式存储在文件系统,单纯为了计算而把数据存储到数据库有点得不偿失。
Python 的 Pandas 提供了丰富的关联运算函数,能更方便的完成文本文件间的关联计算,现在我们就一起来讨论下 Python 的关联处理。
基础关联
所谓关联是指两张数据表通过某个字段或者某些字段之间存在的某种关系,将两张表以某种条件关联起来。SQL 中的关联可以是等值 JOIN 也可以是非等值 JOIN,所谓非等值 JOIN 就是 JOIN 时的过滤条件不一定是相等的,比如 select A.x,B.y from A join B on A.a<B.b。但在实际工作中这种运算是很少的,绝大多数情况都是等值 JOIN,而且即使遇到非等值 JOIN 的情况,多数情况下也可以转换成等值 JOIN。Python 的 Pandas 中的关联函数都是等值关联的,在这里我们也只讨论等值 JOIN 的情况。
关联的方式有内连接、左连接、右连接、全连接。Pandas 使用 merge() 函数的 how 参数完成,其中 inner:内连接,left:左连接,right:右连接,outer:全连接(默认 how=‘inner’)。下面以一些例子加以说明。
合同数据文件和老客户数据文件分别存储在两份文件中,合同数据文件是 2019 年的合同信息,其中的 Client 可能有新客户,部分内容如下:
ContractNo | ActualSale | SellDate | Product | Quantity | Amount | Client | ApplyArea | ApplyMethod |
10961 | 8 | 2019/3/19 | 52 | 6 | 1122 | C67350 | EastChina | SELF_USE |
10962 | 8 | 2019/3/19 | 7 | 45 | 3584 | C67393 | SouthChina | SELF_USE |
10963 | 9 | 2019/3/19 | 60 | 2 | 68 | C27452 | NorthChina | RESELL |
10964 | 3 | 2019/3/20 | 18 | 6 | 2052.5 | C77608 | SouthChina | RESELL |
10965 | 6 | 2019/3/20 | 51 | 16 | 848 | C57885 | EastChina | SELF_USE |
10966 | 4 | 2019/3/20 | 37 | 8 | 1255.6 | C13113 | SouthChina | SELF_USE |
老客户数据文件是历年来经常合作的老客户的信息,部分内容如下:
ID | Name | City | State | Country | SalesGoal |
C908 | ALFKI | Shenyang | Liaoning | China | 3 |
C2262 | ANATR | Dalian | Liaoning | China | 1 |
C4352 | ANTON | Yingkou | Liaoning | China | 10 |
C4842 | AROUT | benxi | Liaoning | China | 6 |
C5505 | BERGS | Shijiazhuang | Hebei | China | 5 |
内连接(inner)
内连接,是两表关联后,保留两表共有的键。
问题一:计算老客户 2019 年的合同总金额
问题分析:由于合同数据中可能有新客户,也可能有老客户没有在 2019 年签订合同,所以需要使用内连接的方式将两表关联。
Python 代码
import pandas as pd contract = pd.read_csv('Contract.csv') client = pd.read_csv('Client.csv') join_inner = pd.merge(contract,client,left_on='Client',right_on='ID') print(join_inner['Amount'].sum()) |
导入 pandas 合同数据 客户数据 内连接
计算合同总金额 |
讨论:本例中关联列在两个 dataframe 中列名不同,不能使用 on 参数,而是使用 left_on,左 dataframe 的关联列名,和 right_on,右 dataframe 的关联列名,此时默认的参数 how=’inner’,对关联结果的 Amount 列求和得到结果。
左连接(left)
左连接,指两表关联后,保留左侧表的全部键,右侧表不具备左侧键的列为 nan。
问题二:计算出各省客户的合同金额,新客户的省份用 unknown 表示。
问题分析:因为不光要求各省老客户的合同金额,还要把新客户作为 unknown 来计算,因此关联的时候需要使用 left 的方式。
Python 代码
import pandas as pd contract = pd.read_csv('Contract.csv') client = pd.read_csv('Client.csv',usecols=['ID','State']) join_left = pd.merge(contract,client,left_on='Client',right_on='ID',how='left') join_left.fillna('unknown',inplace=True) state_amount = join_left.groupby('State',as_index=False).Amount.sum() print(state_amount) |
左连接
‘unknown’填补缺失值
分组计算各省客户合同金额 |
讨论:修改默认参数 how=‘inner’为‘left’,实现左连接,保留新客户的合同信息。后续再用‘unknown’填补缺失值,分组求和得到结果。
右连接和全连接的方式就是修改 how=‘right’或者 how=‘outer’。这里就不再一一举例了。
左排除连接(left_exluding)
左排除连接,指两表关联后,只保留左侧键值未在右侧表的键出现的行,右侧表的列为 nan。
问题三:计算新客户的合同总金额
问题分析,只求新客户,只需要左连接后,排除掉两表共有键的行就可以了。
Python 代码
import pandas as pd contract = pd.read_csv('Contract.csv') client = pd.read_csv('Client.csv',usecols=['ID','State']) join_left = pd.merge(contract,client,left_on='Client',right_on='ID',how='left',indicator=True) join_left_exluding = join_left.query('_merge == "left_only"').drop('_merge', 1) new_client_amount = join_left_exluding.Amount.sum() print(new_client_amount) |
左连接
左排除连接 计算新客户合同金额 |
讨论:左连接时,增加参数 indicator,那些两表共有的键的行为 both,左表独有的键的行为 left_only,右表独有的为 right_only。然后筛选出 left_only 标记的行就得到了新客户的行,直接求和得到结果。
右排除连接和全排除连接和左排除连接类似,这里也不举例说明了。
Pandas 中的关联函数除了 merge,还有 join 和 concat,我们继续以问题一——计算老客户 2019 年的合同总金额作为案例简单介绍一下这两个函数的使用方法。
join 函数
Python 代码
import pandas as pd contract = pd.read_csv('Contract.csv',index_col='Client') client = pd.read_csv('Client.csv',index_col='ID') join_inner = contract.join(client,how='inner') print(join_inner.Amount.sum()) |
导入合同数据,同时设置索引为‘Client’
导入客户数据,同时设置索引为‘ID’ 使用 join 关联两表 |
讨论:join() 函数关联时,只能使用两表的索引,因此需要先将关联列设置成索引。join 时默认的方式 how=‘left’,这里需要修改为‘inner’。最后求和即可。
concat 函数
问题分析:concat()函数关联时,要求关联表的索引是唯一的,这里合同数据的 Client 列有重复,不适合使用 concat() 函数关联,不过为了更好的理解 concat 函数,我们对数据中的客户去重,然后再用 concat 连接。
问题四:求老客户 2019 年第一份合同的总金额。
Python 代码
import pandas as pd contract = pd.read_csv('Contract.csv') client = pd.read_csv('Client.csv',index_col='ID') contract = contract.drop_duplicates('Client', keep='first') contract.set_index('Client',inplace=True) join_concat = pd.concat([contract,client],axis=1, join='inner') print(join_concat.Amount.sum()) |
导入客户数据,同时设置索引为‘ID’ Client列去重 设置 Client 为索引 concat关联
|
讨论:把两个 dataframe 放入列表,axis=1 指按行索引进行关联,默认的 join 方式是 outer,这里改为 inner,最后进行求和。
多字段关联
多字段关联是指两表关联时的键不是单一字段,需要同时关联两个或者更多字段。
现有学生表,部分内容如下:
studentid | name | majorid | classid |