本文概述
你是否尝试过解决Kaggle挑战?如果是, 你可能已经注意到, 在大多数挑战中, 提供给你的数据存在于多个文件中, 而某些列存在于多个文件中。好吧, 首先想到的是什么?当然加入他们!
连接和合并DataFrame是从数据分析和机器学习任务开始的核心过程。它是每个数据分析师或数据科学家都应掌握的工具箱之一, 因为在几乎所有情况下, 数据都来自多个源和文件。你可能需要通过某种联接逻辑将所有数据放在一个位置, 然后开始分析。使用诸如查询语言之类的SQL的人可能知道此任务的重要性。即使你要在某些数据上构建一些机器学习模型, 也可能需要将多个csv文件合并到一个DataFrame中。
值得庆幸的是, 你拥有python中最受欢迎的库, 熊猫来助你一臂之力! pandas提供了各种功能, 可以在连接/合并类型操作的情况下轻松地将Series, DataFrame和Panel对象与各种用于索引和关系代数功能的设置逻辑组合在一起。
在本教程中, 你将练习一些标准技术。更具体地说, 你将学习:
- 沿行和列连接DataFrame。
- 通过不同的联接逻辑(例如左联接, 内联接等)在特定键上合并DataFrame。
- 熊猫提供的时间序列友好合并
在此过程中, 你还将学习加入之前和之后所需的一些技巧。
连接数据帧
首先导入将在整个教程中使用的库:pandas
import pandas as pd
你将在要创建的虚拟DataFrame上执行本教程中的所有操作。要创建一个DataFrame, 你可以使用python字典, 例如:
dummy_data1 = {
'id': ['1', '2', '3', '4', '5'], 'Feature1': ['A', 'C', 'E', 'G', 'I'], 'Feature2': ['B', 'D', 'F', 'H', 'J']}
在这里, dummy_data1词典的关键字是列名, 列表中的值是与每个观察值或行对应的数据。要将其转换为pandas DataFrame, 你将使用pandas的DataFrame()函数以及其column参数来命名你的列:
df1 = pd.DataFrame(dummy_data1, columns = ['id', 'Feature1', 'Feature2'])
df1
id | 功能1 | 功能2 | |
---|---|---|---|
0 | 1 | 一个 | 乙 |
1 | 2 | C | d |
2 | 3 | 和 | F |
3 | 4 | G | H |
4 | 5 | 一世 | Ĵ |
如你所见, 你现在拥有一个包含3列ID, Feature1和Feature2的DataFrame。熊猫在内部另外创建了一个未命名的列作为行标签。与先前的DataFrame df1类似, 你将再创建两个DataFrame df2和df3:
dummy_data2 = {
'id': ['1', '2', '6', '7', '8'], 'Feature1': ['K', 'M', 'O', 'Q', 'S'], 'Feature2': ['L', 'N', 'P', 'R', 'T']}
df2 = pd.DataFrame(dummy_data2, columns = ['id', 'Feature1', 'Feature2'])
df2
id | 功能1 | 功能2 | |
---|---|---|---|
0 | 1 | 该 | 该 |
1 | 2 | 中号 | ñ |
2 | 6 | 的 | P |
3 | 7 | 问 | [R |
4 | 8 | 小号 | Ť |
dummy_data3 = {
'id': ['1', '2', '3', '4', '5', '7', '8', '9', '10', '11'], 'Feature3': [12, 13, 14, 15, 16, 17, 15, 12, 13, 23]}
df3 = pd.DataFrame(dummy_data3, columns = ['id', 'Feature3'])
df3
id | 功能3 | |
---|---|---|
0 | 1 | 12 |
1 | 2 | 13 |
2 | 3 | 14 |
3 | 4 | 15 |
4 | 5 | 16 |
5 | 7 | 17 |
6 | 8 | 15 |
7 | 9 | 12 |
8 | 10 | 13 |
9 | 11 | 23 |
要简单地沿行连接DataFrame, 可以在pandas中使用concat()函数。你将必须在列表中传递数据帧的名称作为concat()函数的参数:
df_row = pd.concat([df1, df2])
df_row
id | 功能1 | 功能2 | |
---|---|---|---|
0 | 1 | 一个 | 乙 |
1 | 2 | C | d |
2 | 3 | 和 | F |
3 | 4 | G | H |
4 | 5 | 一世 | Ĵ |
0 | 1 | 该 | 该 |
1 | 2 | 中号 | ñ |
2 | 6 | 的 | P |
3 | 7 | 问 | [R |
4 | 8 | 小号 | Ť |
你会注意到, 两个DataFrame df1和df2现在沿着该行串联到单个DataFrame df_row中。但是, 行标签似乎是错误的!如果希望行标签根据联接自动调整, 则必须在调用concat()函数时将参数ignore_index设置为True:
df_row_reindex = pd.concat([df1, df2], ignore_index=True)
df_row_reindex
id | 功能1 | 功能2 | |
---|---|---|---|
0 | 1 | 一个 | 乙 |
1 | 2 | C | d |
2 | 3 | 和 | F |
3 | 4 | G | H |
4 | 5 | 一世 | Ĵ |
5 | 1 | 该 | 该 |
6 | 2 | 中号 | ñ |
7 | 6 | 的 | P |
8 | 7 | 问 | [R |
9 | 8 | 小号 | Ť |
现在行标签正确了!
pandas还为你提供了一个选项, 可以在串联后为键标记DataFrame, 以便你可以知道哪些数据来自哪个DataFrame。你可以通过传递其他参数键来实现相同目的, 这些参数键在列表中指定DataFrame的标签名称。在这里, 你将分别使用与x和y相同的键对DataFrames df1和df2执行相同的串联操作。
frames = [df1, df2]
df_keys = pd.concat(frames, keys=['x', 'y'])
df_keys
id | 功能1 | 功能2 | ||
---|---|---|---|---|
X | 0 | 1 | 一个 | 乙 |
1 | 2 | C | d | |
2 | 3 | 和 | F | |
3 | 4 | G | H | |
4 | 5 | 一世 | Ĵ | |
和 | 0 | 1 | 该 | 该 |
1 | 2 | 中号 | ñ | |
2 | 6 | 的 | P | |
3 | 7 | 问 | [R | |
4 | 8 | 小号 | Ť |
提及密钥还可以轻松检索与特定DataFrame对应的数据。你可以使用loc方法检索标签为y的DataFrame df2的数据:
df_keys.loc['y']
id | 功能1 | 功能2 | |
---|---|---|---|
0 | 1 | 该 | 该 |
1 | 2 | 中号 | ñ |
2 | 6 | 的 | P |
3 | 7 | 问 | [R |
4 | 8 | 小号 | Ť |
你还可以将字典传递给concat(), 在这种情况下, 字典键将用于keys参数(除非指定了其他键):
pieces = {'x': df1, 'y': df2}
df_piece = pd.concat(pieces)
df_piece
id | 功能1 | 功能2 | ||
---|---|---|---|---|
X | 0 | 1 | 一个 | 乙 |
1 | 2 | C | d | |
2 | 3 | 和 | F | |
3 | 4 | G | H | |
4 | 5 | 一世 | Ĵ | |
和 | 0 | 1 | 该 | 该 |
1 | 2 | 中号 | ñ | |
2 | 6 | 的 | P | |
3 | 7 | 问 | [R | |
4 | 8 | 小号 | Ť |
值得注意的是, concat()会完整复制数据, 并且连续地重用此函数可能会严重影响性能。如果需要对多个数据集使用该操作, 请使用列表推导。
frames = [ process_your_file(f) for f in files ]
result = pd.concat(frames)
要沿列连接DataFrame, 可以将axis参数指定为1:
df_col = pd.concat([df1, df2], axis=1)
df_col
id | 功能1 | 功能2 | id | 功能1 | 功能2 | |
---|---|---|---|---|---|---|
0 | 1 | 一个 | 乙 | 1 | 该 | 该 |
1 | 2 | C | d | 2 | 中号 | ñ |
2 | 3 | 和 | F | 6 | 的 | P |
3 | 4 | G | H | 7 | 问 | [R |
4 | 5 | 一世 | Ĵ | 8 | 小号 | Ť |
Works数据帧
与DataFrames有关的另一个普遍存在的操作是合并操作。两个DataFrame可能保存有关同一实体的不同种类的信息, 并通过某个共同的功能/列进行链接。为了连接这些DataFrame, pandas提供了诸如concat(), merge(), join()等多个功能。在本节中, 你将练习使用pandas的merge()函数。
你可以将DataFrames df_row(通过沿行串联df1和df2来创建)和df3联接到公共列(或键)ID上。为此, 请将DataFrames的名称和其他参数作为公共列的名称(此处为id)传递给merge()函数:
df_merge_col = pd.merge(df_row, df3, on='id')
df_merge_col
id | 功能1 | 功能2 | 功能3 | |
---|---|---|---|---|
0 | 1 | 一个 | 乙 | 12 |
1 | 1 | 该 | 该 | 12 |
2 | 2 | C | d | 13 |
3 | 2 | 中号 | ñ | 13 |
4 | 3 | 和 | F | 14 |
5 | 4 | G | H | 15 |
6 | 5 | 一世 | Ĵ | 16 |
7 | 7 | 问 | [R | 17 |
8 | 8 | 小号 | Ť | 15 |
你会注意到, DataFrame现在基于两个DataFrame的id列中存在的公共值合并到单个DataFrame中。例如, 此处的ID值1在数据帧df_row中同时包含A, B和K, L, 因此此ID在最终的数据帧df_merge_col中重复了两次, 其中Feature3的重复值12来自数据帧df3。
可能要在其上合并DataFrame的列具有不同的名称(在这种情况下不一样)。对于此类合并, 你将必须将参数left_on指定为左侧DataFrame名称, 将right_on指定为右侧DataFrame名称, 例如:
df_merge_difkey = pd.merge(df_row, df3, left_on='id', right_on='id')
df_merge_difkey
id | 功能1 | 功能2 | 功能3 | |
---|---|---|---|---|
0 | 1 | 一个 | 乙 | 12 |
1 | 1 | 该 | 该 | 12 |
2 | 2 | C | d | 13 |
3 | 2 | 中号 | ñ | 13 |
4 | 3 | 和 | F | 14 |
5 | 4 | G | H | 15 |
6 | 5 | 一世 | Ĵ | 16 |
7 | 7 | 问 | [R | 17 |
8 | 8 | 小号 | Ť | 15 |
你还可以通过将Series或dict传递给append()函数来将行追加到DataFrame, 该函数返回一个新的DataFrame:
add_row = pd.Series(['10', 'X1', 'X2', 'X3'], index=['id', 'Feature1', 'Feature2', 'Feature3'])
df_add_row = df_merge_col.append(add_row, ignore_index=True)
df_add_row
id | 功能1 | 功能2 | 功能3 | |
---|---|---|---|---|
0 | 1 | 一个 | 乙 | 12 |
1 | 1 | 该 | 该 | 12 |
2 | 2 | C | d | 13 |
3 | 2 | 中号 | ñ | 13 |
4 | 3 | 和 | F | 14 |
5 | 4 | G | H | 15 |
6 | 5 | 一世 | Ĵ | 16 |
7 | 7 | 问 | [R | 17 |
8 | 8 | 小号 | Ť | 15 |
9 | 10 | X1 | X2 | X3 |
加入数据帧
在本节中, 你将练习基于各种常见列/键的各种合并逻辑, 可用于合并pandas DataFrame。这些联接背后的逻辑与联接表时在SQL中的逻辑非常相似。
完全外部加入
FULL OUTER JOIN合并左右外部联接的结果。联接的DataFrame将包含两个DataFrame的所有记录, 并填写NaN以弥补任一侧缺少的匹配项。你可以通过在merge()函数中将how参数指定为external来执行完全外部联接:
df_outer = pd.merge(df1, df2, on='id', how='outer')
df_outer
id | Feature1_x | Feature2_x | Feature1_y | Feature2_y | |
---|---|---|---|---|---|
0 | 1 | 一个 | 乙 | 该 | 该 |
1 | 2 | C | d | 中号 | ñ |
2 | 3 | 和 | F | NaN | NaN |
3 | 4 | G | H | NaN | NaN |
4 | 5 | 一世 | Ĵ | NaN | NaN |
5 | 6 | NaN | NaN | 的 | P |
6 | 7 | NaN | NaN | 问 | [R |
7 | 8 | NaN | NaN | 小号 | Ť |
你会注意到, 生成的DataFrame在两个表中都具有NaN值的两个表中的所有条目, 以便在任一侧丢失匹配项。但是, 还有一点需要注意的是后缀, 该后缀被附加到列名以显示哪个列来自哪个DataFrame。默认后缀是x和y, 但是, 你可以通过在merge()函数中指定后缀参数来修改它们:
df_suffix = pd.merge(df1, df2, left_on='id', right_on='id', how='outer', suffixes=('_left', '_right'))
df_suffix
id | Feature1_left | Feature2_left | Feature1_right | Feature2_right | |
---|---|---|---|---|---|
0 | 1 | 一个 | 乙 | 该 | 该 |
1 | 2 | C | d | 中号 | ñ |
2 | 3 | 和 | F | NaN | NaN |
3 | 4 | G | H | NaN | NaN |
4 | 5 | 一世 | Ĵ | NaN | NaN |
5 | 6 | NaN | NaN | 的 | P |
6 | 7 | NaN | NaN | 问 | [R |
7 | 8 | NaN | NaN | 小号 | Ť |
内部联接
INNER JOIN仅生成在DataFrame A和DataFrame B中都匹配的记录集。你必须在merge()函数的how参数中传递inner来进行内部联接:
df_inner = pd.merge(df1, df2, on='id', how='inner')
df_inner
id | Feature1_x | Feature2_x | Feature1_y | Feature2_y | |
---|---|---|---|---|---|
0 | 1 | 一个 | 乙 | 该 | 该 |
1 | 2 | C | d | 中号 | ñ |
正确加入
RIGHT JOIN会从DataFrame B(右DataFrame)生成完整的记录集, 并在DataFrame A(左DataFrame)中生成匹配的记录(如果有)。如果没有匹配项, 则右侧将包含null。你必须直接在merge()函数的how参数中进行正确的连接:
df_right = pd.merge(df1, df2, on='id', how='right')
df_right
id | Feature1_x | Feature2_x | Feature1_y | Feature2_y | |
---|---|---|---|---|---|
0 | 1 | 一个 | 乙 | 该 | 该 |
1 | 2 | C | d | 中号 | ñ |
2 | 6 | NaN | NaN | 的 | P |
3 | 7 | NaN | NaN | 问 | [R |
4 | 8 | NaN | NaN | 小号 | Ť |
左加入
LEFT JOIN会从DataFrame A(左侧DataFrame)生成完整的记录集, 并在DataFrame B(右侧DataFrame)中生成匹配的记录(如果有)。如果没有匹配项, 则左侧将包含null。你必须在merge()函数的how参数中向左传递以进行左连接:
df_left = pd.merge(df1, df2, on='id', how='left')
df_left
id | Feature1_x | Feature2_x | Feature1_y | Feature2_y | |
---|---|---|---|---|---|
0 | 1 | 一个 | 乙 | 该 | 该 |
1 | 2 | C | d | 中号 | ñ |
2 | 3 | 和 | F | NaN | NaN |
3 | 4 | G | H | NaN | NaN |
4 | 5 | 一世 | Ĵ | NaN | NaN |
加入索引
有时你可能必须对索引或行标签执行联接。为此, 你必须将right_index(用于右侧DataFrame的索引)和left_index(用于左侧DataFrame的索引)指定为True:
df_index = pd.merge(df1, df2, right_index=True, left_index=True)
df_index
id_x | Feature1_x | Feature2_x | id_y | Feature1_y | Feature2_y | |
---|---|---|---|---|---|---|
0 | 1 | 一个 | 乙 | 1 | 该 | 该 |
1 | 2 | C | d | 2 | 中号 | ñ |
2 | 3 | 和 | F | 6 | 的 | P |
3 | 4 | G | H | 7 | 问 | [R |
4 | 5 | 一世 | Ĵ | 8 | 小号 | Ť |
时间序列友好合并
熊猫提供了用于合并时间序列数据帧的特殊功能。也许最有用和最受欢迎的是merge_asof()函数。 merge_asof()与有序左联接相似, 不同之处在于你匹配的是最接近的键而不是相等的键。对于左侧DataFrame中的每一行, 请选择右侧DataFrame中其on键小于左侧键的最后一行。两个DataFrame必须按键排序。
可选地, asof合并可以执行逐组合并。除on键上最接近的匹配项外, 此键均等地匹配by键。
例如, 你可能有交易和报价, 并且想要将它们合并。在这里, 左边的DataFrame被选作交易, 右边的DataFrame被选为报价。它们在关键时间开始合并, 并通过股票代码进行分组合并。
trades = pd.DataFrame({
'time': pd.to_datetime(['20160525 13:30:00.023', '20160525 13:30:00.038', '20160525 13:30:00.048', '20160525 13:30:00.048', '20160525 13:30:00.048']), 'ticker': ['MSFT', 'MSFT', 'GOOG', 'GOOG', 'AAPL'], 'price': [51.95, 51.95, 720.77, 720.92, 98.00], 'quantity': [75, 155, 100, 100, 100]}, columns=['time', 'ticker', 'price', 'quantity'])
quotes = pd.DataFrame({
'time': pd.to_datetime(['20160525 13:30:00.023', '20160525 13:30:00.023', '20160525 13:30:00.030', '20160525 13:30:00.041', '20160525 13:30:00.048', '20160525 13:30:00.049', '20160525 13:30:00.072', '20160525 13:30:00.075']), 'ticker': ['GOOG', 'MSFT', 'MSFT', 'MSFT', 'GOOG', 'AAPL', 'GOOG', 'MSFT'], 'bid': [720.50, 51.95, 51.97, 51.99, 720.50, 97.99, 720.50, 52.01], 'ask': [720.93, 51.96, 51.98, 52.00, 720.93, 98.01, 720.88, 52.03]}, columns=['time', 'ticker', 'bid', 'ask'])
trades
时间 | 股票代码 | 价钱 | 数量 | |
---|---|---|---|---|
0 | 2016-05-25 13:30:00.023 | 微软 | 51.95 | 75 |
1 | 2016-05-25 13:30:00.038 | 微软 | 51.95 | 155 |
2 | 2016-05-25 13:30:00.048 | 高格 | 720.77 | 100 |
3 | 2016-05-25 13:30:00.048 | 高格 | 720.92 | 100 |
4 | 2016-05-25 13:30:00.048 | AAPL | 98.00 | 100 |
quotes
时间 | 股票代码 | 出价 | 问 | |
---|---|---|---|---|
0 | 2016-05-25 13:30:00.023 | 高格 | 720.50 | 720.93 |
1 | 2016-05-25 13:30:00.023 | 微软 | 51.95 | 51.96 |
2 | 2016-05-25 13:30:00.030 | 微软 | 51.97 | 51.98 |
3 | 2016-05-25 13:30:00.041 | 微软 | 51.99 | 52.00 |
4 | 2016-05-25 13:30:00.048 | 高格 | 720.50 | 720.93 |
5 | 2016-05-25 13:30:00.049 | AAPL | 97.99 | 98.01 |
6 | 2016-05-25 13:30:00.072 | 高格 | 720.50 | 720.88 |
7 | 2016-05-25 13:30:00.075 | 微软 | 52.01 | 52.03 |
df_merge_asof = pd.merge_asof(trades, quotes, on='time', by='ticker')
df_merge_asof
时间 | 股票代码 | 价钱 | 数量 | 出价 | 问 | |
---|---|---|---|---|---|---|
0 | 2016-05-25 13:30:00.023 | 微软 | 51.95 | 75 | 51.95 | 51.96 |
1 | 2016-05-25 13:30:00.038 | 微软 | 51.95 | 155 | 51.97 | 51.98 |
2 | 2016-05-25 13:30:00.048 | 高格 | 720.77 | 100 | 720.50 | 720.93 |
3 | 2016-05-25 13:30:00.048 | 高格 | 720.92 | 100 | 720.50 | 720.93 |
4 | 2016-05-25 13:30:00.048 | AAPL | 98.00 | 100 | NaN | NaN |
如果仔细观察, 你会发现NaN出现在AAPL代码行中的原因。由于正确的DataFrame报价没有任何时间值小于13:30:00.048(左表中的时间), 因此APN报价中引入了NaN。
你还可以为时间列设置预定义的公差级别。假设你只希望在报价时间和交易时间之间的2ms之内进行asof合并, 那么你将必须指定公差参数:
df_merge_asof_tolerance = pd.merge_asof(trades, quotes, on='time', by='ticker', tolerance=pd.Timedelta('2ms'))
df_merge_asof_tolerance
时间 | 股票代码 | 价钱 | 数量 | 出价 | 问 | |
---|---|---|---|---|---|---|
0 | 2016-05-25 13:30:00.023 | 微软 | 51.95 | 75 | 51.95 | 51.96 |
1 | 2016-05-25 13:30:00.038 | 微软 | 51.95 | 155 | NaN | NaN |
2 | 2016-05-25 13:30:00.048 | 高格 | 720.77 | 100 | 720.50 | 720.93 |
3 | 2016-05-25 13:30:00.048 | 高格 | 720.92 | 100 | 720.50 | 720.93 |
4 | 2016-05-25 13:30:00.048 | AAPL | 98.00 | 100 | NaN | NaN |
注意上述结果与先前结果之间的区别。如果时间公差不匹配2ms, 则行不会合并。
总结
欢呼!你到了本教程的结尾。在本教程中, 你学习了使用pandas库的concat()和merge()函数基于几种逻辑来串联和合并DataFrame。最后, 你还练习了特殊功能merge_asof()来合并时间序列DataFrame。在此过程中, 你还学会了如何使用DataFrames的索引。你还可以探索其他几种选择, 以在熊猫中加入DataFrames, 我鼓励你查看其出色的文档。探索愉快!
本教程使用以下资源来帮助编写它:
- https://pandas.pydata.org/pandas-docs/stable/merging.html
如果你想了解有关熊猫的更多信息, 请参加srcmini的熊猫基础课程。
评论前必须登录!
注册