merge 全面的实用指南
Merge 能够将来自不同来源的数据组合成一个统一的结构。在处理表格数据时,这是一个必不可少的操作,因为将所有数据存储大多都。
了解如何在Pandas中有效地合并DataFrame是任何数据科学家或分析师的一项重要技能。
Merge 意味着根据相同列中的值合并DataFrame。
在这篇文章中,我们将通过一组全面的20个例子,来阐明合并操作的细微差别。我们将从基本的合并功能开始,逐步深入到更复杂的场景中,涵盖所有关于用Pandas合并DataFrames的细节。
我们将涉及的函数是:
-
merge -
merge_asof -
merge_ordered
让我们先创建两个DataFrames,在例子中使用。
import numpy as np
import pandas as pd
names = pd.DataFrame(
{
"id": [1, 2, 3, 4, 10],
"name": ["Emily", "Jane", "Joe", "Matt", "Lucas"],
"age": np.random.randint(20, 30, size=5)
}
)
scores = pd.DataFrame(
{
"id": np.arange(1, 8),
"score": np.random.randint(80, 100, size=7),
"class": list("ABCAACA")
}
)

names 和 scores DataFrame
示例 1
merge 函数的基本语法如下。参数 "on"表示在关联行时要使用的列。
merged_df = names.merge(scores, on="id")
示例 2--参数
有不同的合并类型。how参数从以下类型中定义它:
-
left: 只使用左边数据框架中的键值 -
right:只使用右边数据框架中的键值 -
outer:使用两个数据框架的键的联合。 -
inner:使用两个数据框架的键的得到交集 -
cross:使用两个数据框架的笛卡尔乘积。
how参数的默认值是inner,所以在前面的例子中,合并后的DataFrame包含一个键的交集。
键值是用on参数指定的列中的值。
让我们做一个left的合并。
merged_df = names.merge(scores, on="id", how="left")
合并后的数据框架包括左边数据框架的所有键。不匹配的行用 "NaN"填充,即标准的缺失值。
示例 3--右合并
它与左合并相反,但我不建议使用右合并,因为它可以通过改变DataFrame的顺序和使用左合并来实现。
merged_df = names.merge(scores, on="id", how="left")
merged_df = scores.merge(names, on="id", how="right")
示例 4-- 外(outer)合并
merged_df = names.merge(scores, on="id", how="outer")
合并后的数据框架包括两个数据框架的所有 key。
示例 5 -- 指标参数(indicator)
"indicator"参数在合并后的数据框架中创建一个列,指示行中的关键值来自哪里。
-
both: 每行中对应的key值存在于两个源数据框架中 -
left_only: 每行中对应的key值只存在原左边的数据框 -
right_only: 每行中对应的key值只存在原右边的数据框
merged_df = names.merge(scores, on="id", how="outer", indicator=True)
示例 6 -- 指示器参数
indicator参数也是以字符串值为参数,作为合并后指示列的名称。
merged_df = names.merge(scores, on="id", how="left", indicator="source")
示例 7 -- left_on和right_on参数
如果用于合并DataFrames的列有不同的名字,我们可以使用left_on和right_on参数。使用场景两个源dataframe的 key 的列名有差异。如,表1中为 id,表2中为 id_number
scores = scores.rename(columns={"id": "id_number"})
merged_df = names.merge(scores, left_on="id", right_on="id_number")
示例 8 --多列合并
我们将为这个例子创建两个新的DataFrames。适用场景为多 key 进行关联。
products = pd.DataFrame({"pg": ["A", "A", "A", "B", "B", "B"],
"id": [101, 102, 103, 101, 102, 104],
"price": np.random.randint(50, 80, size=6),
"cost": np.random.randint(40, 50, size=6),
"discount": [0.1, 0.1, 0, 0, 0.2, 0] } )
sales = pd.DataFrame({ "pg": ["A", "A", "A", "B", "B", "B"],
"id": [101, 102, 105, 101, 102, 106],
"sales_qty": np.random.randint(1, 10, size=6), "discount": [0, 0.1, 0.1, 0.2, 0, 0] } )
为了合并多列的DataFrames,我们把列名写成一个Python list。
merged_df = products.merge(sales, on=["pg", "id"])
示例 9 -- suffixes参数
用于标记两个dataframe中同名的字段,
在前面的例子中,合并后的DataFrame有discount_x和discount_y两列。x和y的后缀是用来分隔两个DataFrame中存在的同名列的。x用于左边的数据框架,y用于右边的数据框架。
我们可以使用自定义后缀来使输出更容易理解。
merged_df = products.merge(sales, on=["pg", "id"], suffixes=["_products", "_sales"])
示例 10 -- 多列
就像on参数一样, right_on和left_on参数在有不同列名的情况下使用一个列表作为参数。
sales = sales.rename(columns={"id": "product_id"})
merged_df = products.merge(sales,
left_on=["pg", "id"],
right_on = ["pg","product_id"],
how="left",
suffixes=["_products", "_sales"])
示例 11 -- 根据索引合并
我们也可以根据索引值合并DataFrames。我们将为这个例子创建两个新的DataFrames。
df1 = pd.DataFrame(np.random.randint(0, 10, size=(5, 4)),
columns=list("ABCD"))
df2 = pd.DataFrame(np.random.randint(0, 10, size=(5, 4)),
columns=list("EFGH"), index=[2, 3, 4, 5, 6])
正如我们在上面的截图中看到的,DataFrames有不同的索引值。一个从0开始,另一个从2开始。
为了合并索引,我们使用left_index和right_index参数。
merged_df = df1.merge(df2, left_index=True, right_index=True)
由于我们使用了inner合并,合并后的DataFrame只包括两个DataFrame中存在的index。
示例12 -- how参数与索引的合并
在对索引进行合并时,我们也可以使用how参数。
merged_df = df1.merge(df2, left_index=True, right_index=True, how="left")
示例13 -- 合并时间序列数据
时间序列数据可能包括在非常短的时间段内进行的测量(例如,在秒级)。因此,当我们合并两个由时间序列数据组成的DataFrames时,我们可能会遇到测量值偏离一到两秒的情况。
对于这种情况,Pandas通过merge_asof函数提供了一种 "智能"的合并方式。
假设我们正在合并数据框A和B。如果左边数据框中的某一行在右边数据框中没有匹配的行,merge_asof允许取一个值与左边数据框中的值接近的行。
这类似于左合并,只是我们在最接近的键上进行匹配,而不是相等的键。两个数据框架都必须按键进行排序。
对于左边DataFrame中的每一行:
-
backward搜索右边数据框中"on"键小于或等于左边的键,选择key值最近的值 -
forward搜索右边数据框中"on"键大于或等于左边的键,选择key值最近的值 -
nearest搜索选择右边DataFrame中 "on"键与左边键的绝对距离最近的一行。
让我们创建两个包含时间序列数据的新DataFrame。
df1 = pd.DataFrame(
{
"time": pd.date_range(start="2022-12-09", periods=7, freq="2S"),
"left_value": np.round(np.random.random(7), 2)
}
)
df2 = pd.DataFrame(
{
"time": pd.date_range(start="2022-12-09", periods=6, freq="3S"),
"right_value": np.round(np.random.random(6), 2)
}
)
时间栏中的一些数值是重叠的,而另一些则相差几秒钟。
让我们看看当我们合并它们时会发生什么。默认是backward
merged_df = pd.merge_asof(df1, df2, on="time")
-
右边的数据框架(df2)没有00:00:02的值,所以在合并的数据框架中,00:00:00的值被作为右边的值。 -
右边的数据框架(df2)没有00:00:04的值,所以在合并的数据框架中,00:00:03的值被作为右边的值。
示例14 -- 方向(direction)参数
在前面的例子中,merge_asof函数为不匹配的行寻找之前的值,因为方向参数的默认值是 "backward"。
让我们把它改为 "nearest",看看会发生什么。
merged_df = pd.merge_asof(df1, df2, on="time", direction="nearest")
第一行的右边数值是0.36,因为下一个数值(00:00:03)比前一个数值(00:00:00)更接近左边DataFrame中的数值(00:00:02)。
示例15 -- 公差(tolerance)参数
我们还可以设置一个公差,在检查前一个和后一个值时使用。
在下面的例子中,方向是向前的,所以下一个值会被检查为不匹配的行。我们还设置了一个1秒的容差,因此,为了使用下一个值,它最多偏离1秒。
merged_df = pd.merge_asof(
df1,
df2,
on="time",
direction="forward",
tolerance=pd.Timedelta("1s")
)
看看合并后的DataFrame中的第三和第六行。右边的值是 "NaN",因为右边DataFrame中的下一个值在这些行中偏离了2秒。
-
左边:00:00:04,右边的下一个值:00:00:06 -
左边:00:00:10,右边的下一个值:00:00:12
示例16 -- allow_exact_matches参数
我们还可以选择不允许在合并后的数据框架中出现精确匹配。默认情况下,精确匹配适用于合并数据框,但可以使用 "allow_exact_matches"参数来改变。on 条件只选 小于或大于,不关联等于。
merged_df = pd.merge_asof(df1, df2, on="time", allow_exact_matches=False)
第一行的时间值是相同的,但是合并后的DataFrame在第一行的右边值栏有一个NaN值,因为我们把allow_exact_matches参数的值设置为False。
示例17 -- by参数
在合并数据点时,"by"参数可用于将组与上一个或下一个值分开。
让我们给我们的DataFrames添加一个组列。
df1["group"] = ["AA"] * 4 + ["BB"] * 3
df2["group"] = ["AA"] * 3 + ["BB"] * 3
示例18 - 有序合并
merge_ordered函数对有序数据进行合并,可选择填充/插值。它是为诸如时间序列等有序数据设计的。
通过一个例子可以更容易地理解它:
merged_df = pd.merge_asof(df1, df2, on="time", by="group")
BB组第一行的右边数值是NaN。我们是根据 "backward"的方向进行合并的,而且前一个值属于不同的组。
示例18 -- 有序合并(merge_ordered)
merge_ordered 函数对有序数据进行合并,可选择填充/插值。它是为诸如时间序列等有序数据设计的。
通过一个例子可以更容易地理解它:
merged_df = pd.merge_ordered(df1, df2)
这些行是按时间列排序的。如果其中一个DataFrame没有特定的时间值,那么来自该DataFrame的列将被填充为NaN。
示例19 - fill_method参数
当使用merge_ordered进行有序合并时, 我们可以使用fill_method参数来定义一个插值方法。
默认值是NaN,我们唯一可以使用的选项是 "ffill",意思是向前填充。
merged_df = pd.merge_ordered(df1, df2, fill_method="ffill")
将输出结果与前面的例子进行比较,你会注意到NaN值是如何被替换成以前的值的。
示例 20 -- left_by 参数
我们也可以在每个组内分别进行有序合并。参数 "left_by "按组列将左边的DataFrame分组,并与右边的DataFrame逐个合并。
merged_df = pd.merge_ordered(df1, df2, fill_method="ffill", left_by="group")
与前面的例子不同,"BB "组第一行的右边数值是NaN,因为我们不能使用其他组的数值。
最后的话
通过这20个例子,我们已经探索了各种合并场景,从最简单的到更复杂的。有了这些实用的例子,你就可以准备好应对任何合并任务了。

