两张表,一个键
真实数据很少只放在一张表里。客户在一个数据框里,他们的订单在另一个数据框里,而“每位客户花了多少钱?”这个问题两者都要用到。把它们连接起来的是键:两张表中都有的一列,用来标识同一个实体。按键组合表叫作连接(join,SQL 的说法)或合并(merge,base R 的说法),其实是同一种操作。
下面是本页其余部分要用到的两张表。注意 5 号客户下了订单,却不在客户表中;2 号和 4 号客户则从未下过单:
这些不匹配是故意安排的:连接如何处理不匹配的行,正是区分各种连接类型的关键。
merge():默认是内连接
merge(x, y, by = "key") 会匹配键相等的行,并把它们的列拼接在一起。默认情况下,它只保留两张表中都存在的键,也就是内连接:
返回了三行。Ana 出现了两次:她有两笔订单,而连接会为每一对匹配都产生一行输出。Ben 和 Dana 消失了(没有订单),5 号客户那笔来历不明的订单也消失了(没有对应客户)。内连接会悄悄丢弃两张表中不匹配的行:当你只关心完整的配对时,这正是你想要的;当你不是这样想时,它就是一个悄无声息的数据丢失 bug。
左连接、右连接和全连接:all.x、all.y、all
要保留不匹配的行,就要说明哪张表的行不能丢。all.x = TRUE 保留第一张表的每一行,这就是左连接,实际中用得最多的连接:
现在 Ben 和 Dana 保留了下来,它们的 amount 为 NA,意思是“这位客户存在,但没有匹配的订单”。这些 NA 是有信息量的,不是垃圾:is.na(result$amount) 恰好就是从未下单的客户名单(如何处理它们见缺失值文档)。其余几种变体只是把同一个思路换了个方向:all.y = TRUE 保留第二张表的每一行(右连接,来自未知的 5 号客户的订单会保留下来,name 为 NA),all = TRUE 则保留两边的全部内容(全连接)。
选择时问自己:哪张表的行我不能丢? 用查找表的数据充实一张主表,用左连接,把主表放在前面。要从两个方向核对数据,用全连接。
键名不同:by.x 和 by.y
不同的表很少在命名上保持一致:一张叫 id,另一张叫 customer_id。不必重命名,把两个名称都告诉 merge() 即可:
输出中的键沿用第一张表的名称。相关的一点:如果两张表有同名的非键列(比如都有 date),merge 会给它们加上后缀,变成 date.x 和 date.y。尽快给它们重命名,这样的列名读起来很糟糕。
dplyr 的连接函数
dplyr 为每种连接类型都提供了一个专门的动词,所以意图体现在函数名里,而不是体现在开关参数中(这是静态代码块;沙盒只运行 base R):
library(dplyr)
left_join(customers, orders, by = "id")
inner_join(customers, orders, by = "id")
full_join(customers, orders, by = "id")
left_join(customers, orders, by = c("id" = "customer_id")) # different names
除了可读性更好,left_join() 还会保留第一张表的行顺序(merge() 会按键重新排序),并且在出现多对多匹配时大声警告。动词家族的介绍见 dplyr 入门。
被低估的成员是 anti_join():它返回第一张表中在第二张表里没有匹配的行,不添加任何列,只给出剩下的那些:
anti_join(customers, orders, by = "id") # customers who never ordered
“哪些行没匹配上?”这个问题在数据清洗中经常出现(未匹配的 ID、孤立的记录、查找失败),anti_join() 一次调用就能回答,而 base R 需要写 customers[!(customers$id %in% orders$id), ]。
堆叠行:rbind()
连接组合的是两张表的列。如果你手上是两批同类的行,比如一月的订单和二月的订单,就用 rbind() 把它们堆叠起来:
要求很严格:两个数据框必须有相同的列名(顺序可以不同,rbind 按名称匹配)。缺少一列或多出一列会直接报错,而不是用 NA 填充。当各批数据的列已经不一致时,dplyr 的 bind_rows() 更宽容:它按名称对齐,并用 NA 填补空缺。
重复键导致的行数爆炸
经典的连接事故:你以为是唯一的键,其实并不唯一,而且是在两张表中都不唯一。每个匹配都会和每个匹配配对,行数成倍增加:
两行连接两行得到四行:每个 x 都和每个 y 配对。在真实数据上,一张 10,000 行的表就是这样变成 300万行,营收总额也随之翻倍。merge() 会悄无声息地这样做。防御靠的是习惯:连接之前,检查你以为唯一的键,anyDuplicated(customers$id) 应该是 0;连接之后,用 nrow() 核对一下行数是否符合预期。
要点回顾
merge(x, y, by = "key")是内连接:不匹配的行会悄悄消失。all.x = TRUE(左连接)、all.y = TRUE(右连接)、all = TRUE(全连接)会保留不匹配的行,并用NA填充。- 键名不同:base R 用
by.x/by.y,dplyr 用by = c("a" = "b")。 - dplyr 为每种连接都起了名字;
anti_join()(返回没有匹配上的行)是被低估的那个。 rbind()堆叠形状相同的表;列必须按名称一一对应。- 重复的键会悄悄让行数倍增:连接前检查
anyDuplicated(),连接后检查nrow()。
下一篇:用 pivot_longer() 和 pivot_wider() 在宽格式和长格式之间转换。
常见问题
R 语言怎么合并两个数据框?
使用 merge(x, y, by = "key"),并指定共同的键列。默认情况下它执行内连接:只有键同时出现在两个数据框中的行才会保留。加上 all.x = TRUE 是左连接,all.y = TRUE 是右连接,all = TRUE 是全连接。
R 语言怎么做左连接?
base R:merge(x, y, by = "key", all.x = TRUE) 会保留 x 的每一行,在没有匹配的地方用 NA 填充 y 的列。dplyr:left_join(x, y, by = "key"),结果相同,而且还会保留 x 原来的行顺序,而 merge() 不会。
键列名称不同时,R 语言怎么合并数据框?
把两个名称都告诉 merge():merge(x, y, by.x = "id", by.y = "customer_id")。在 dplyr 中,等价写法是 left_join(x, y, by = c("id" = "customer_id")),或者使用新的辅助函数:by = join_by(id == customer_id)。
R 语言中 merge 和 rbind 有什么区别?
它们在不同的维度上合并。merge() 按键匹配两张表的行,并把它们的列组合起来,也就是连接。rbind() 把一张表的行堆叠到另一张表下面,两者必须有相同的列名。列完全相同的月度文件要用 rbind();客户加订单要用 merge()。