在保持原始行顺序的同时合并两个数据框
Posted
技术标签:
【中文标题】在保持原始行顺序的同时合并两个数据框【英文标题】:Merge two data frames while keeping the original row order 【发布时间】:2013-07-26 12:40:53 【问题描述】:我想合并两个数据框,保持其中一个的原始行顺序(在下面的示例中为df.2
)。
以下是一些示例数据(class
列中的所有值都在两个数据框中定义):
df.1 <- data.frame(class = c(1, 2, 3), prob = c(0.5, 0.7, 0.3))
df.2 <- data.frame(object = c('A', 'B', 'D', 'F', 'C'), class = c(2, 1, 2, 3, 1))
如果我这样做:
merge(df.2, df.1)
输出是:
class object prob
1 1 B 0.5
2 1 C 0.5
3 2 A 0.7
4 2 D 0.7
5 3 F 0.3
如果我添加sort = FALSE
:
merge(df.2, df.1, sort = F)
结果是:
class object prob
1 2 A 0.7
2 2 D 0.7
3 1 B 0.5
4 1 C 0.5
5 3 F 0.3
但我想要的是:
class object prob
1 2 A 0.7
2 1 B 0.5
3 2 D 0.7
4 3 F 0.3
5 1 C 0.5
【问题讨论】:
问题没有很好的定义。如果合并结果中的行数比 df.2 多或少怎么办? 不可能,因为我使用 df.2 作为合并函数中的第一个变量... 没有。这是错误的。仍然不好定义。假设您在 df;2 中有一个在 df.1 中不存在的类。例如:df.2<-data.frame(object=c('A','B','D','F','C'), class=c(2,1,2,4,1))
好吧,也许编辑更精确。无论如何,PAC 的答案可以完成我正在寻找的工作。谢谢帮助我改进问题。
How can I merge and maintain the row order of one input?的可能重复
【参考方案1】:
您只需要创建一个变量,它给出 df.2 中的行号。然后,一旦你合并了你的数据,你就可以根据这个变量对新的数据集进行排序。这是一个例子:
df.1<-data.frame(class=c(1,2,3), prob=c(0.5,0.7,0.3))
df.2<-data.frame(object=c('A','B','D','F','C'), class=c(2,1,2,3,1))
df.2$id <- 1:nrow(df.2)
out <- merge(df.2,df.1, by = "class")
out[order(out$id), ]
【讨论】:
【参考方案2】:查看 plyr 包中的 join 函数。它类似于合并,但它允许您保持其中一个数据集的行顺序。总的来说,它比合并更灵活。
使用您的示例数据,我们将像这样使用join
:
> join(df.2,df.1)
Joining by: class
object class prob
1 A 2 0.7
2 B 1 0.5
3 D 2 0.7
4 F 3 0.3
5 C 1 0.5
这里有几个链接描述了对合并函数的修复,以保持行顺序:
http://www.r-statistics.com/2012/01/merging-two-data-frame-objects-while-preserving-the-rows-order/
http://r.789695.n4.nabble.com/patching-merge-to-allow-the-user-to-keep-the-order-of-one-of-the-two-data-frame-objects-merged-td4296561.html
【讨论】:
在这里展示解决方案,不要只是引用它们。链接在互联网上很快就会失效.. 这些链接中的每一个都是对合并功能问题的深入讨论,因此我将它们保留为链接。 (但相反,我将再次在 plyr 包中插入 join 函数的插件。) 我(和join
s 文档)不同意它比merge
“更灵活”:“加入通常比合并快,尽管它的功能有点少 - 它目前不提供重命名输出或合并 x 和 y 数据帧中不同变量的方法。”
“它允许您保持其中一个数据集的行顺序” 更准确地说,根据?join
:“与合并不同,它保留了x 无论使用什么连接类型。如果需要,y 中的行将被添加到底部。"【参考方案3】:
感谢@PAC,我想出了这样的事情:
merge_sameord = function(x, y, ...)
UseMethod('merge_sameord')
merge_sameord.data.frame = function(x, y, ...)
rstr = paste(sample(c(0:9, letters, LETTERS), 12, replace=TRUE), collapse='')
x[, rstr] = 1:nrow(x)
res = merge(x, y, all.x=TRUE, sort=FALSE, ...)
res = res[order(res[, rstr]), ]
res[, rstr] = NULL
res
这假设您要保留第一个数据帧的顺序,并且合并的数据帧将具有与第一个数据帧相同的行数。它将为您提供干净的数据框,而无需额外的列。
【讨论】:
【参考方案4】:您还可以查看 Hadley 的 dplyr
包中的 inner_join
函数(plyr
的下一次迭代)。它保留了第一个数据集的行顺序。您所需解决方案的细微差别在于它还保留了第一个数据集的原始列顺序。所以它不一定将我们用于合并的列放在第一个位置。
使用上面的示例,inner_join
结果如下所示:
inner_join(df.2,df.1)
Joining by: "class"
object class prob
1 A 2 0.7
2 B 1 0.5
3 D 2 0.7
4 F 3 0.3
5 C 1 0.5
【讨论】:
【参考方案5】:accepted answer 在使用merge
时提出了一种手动保持秩序的方法,这种方法在大多数情况下都有效,但需要不必要的手动工作。这个解决方案出现在How to ddply() without sorting? 的后面,它处理了保持秩序的问题,但在拆分-应用-组合上下文中:
这在不久前出现在 plyr 邮件列表中(由@kohske 提出),这是 Peter Meilstrup 为有限情况提供的解决方案:
#Peter's version used a function gensym to
# create the col name, but I couldn't track down
# what package it was in.
keeping.order <- function(data, fn, ...)
col <- ".sortColumn"
data[,col] <- 1:nrow(data)
out <- fn(data, ...)
if (!col %in% colnames(out)) stop("Ordering column not preserved by function")
out <- out[order(out[,col]),]
out[,col] <- NULL
out
所以现在你可以使用这个通用的keeping.order
函数来保持merge
调用的原始行顺序:
df.1<-data.frame(class=c(1,2,3), prob=c(0.5,0.7,0.3))
df.2<-data.frame(object=c('A','B','D','F','C'), class=c(2,1,2,3,1))
keeping.order(df.2, merge, y=df.1, by = "class")
根据要求,这将产生:
> keeping.order(df.2, merge, y=df.1, by = "class")
class object id prob
3 2 A 1 0.7
1 1 B 2 0.5
4 2 D 3 0.7
5 3 F 4 0.3
2 1 C 5 0.5
所以keeping.order
有效地自动化了接受答案中的方法。
【讨论】:
【参考方案6】:base 中可能有更有效的方法。这将是相当简单的函数。
varorder <- names(mydata) # --- Merge
mydata <- merge(mydata, otherData, by="commonVar")
restOfvars <- names(mydata[!(names(mydata) %in% varorder)])
mydata[c(varorder,restOfvars)]
【讨论】:
【参考方案7】:来自data.table v1.9.5+,你可以这样做:
require(data.table) # v1.9.5+
setDT(df.1)[df.2, on="class"]
通过为df.2
中的每一行查找df.1
中的匹配行并提取相应的列,对列class
执行连接。
【讨论】:
是否在文档中明确说明保留行顺序的任何地方? 我的某些数据似乎没有。【参考方案8】:为了完整起见,在连接中更新也会保留原始行顺序。如果只有几列要附加,这可能是Arun's data.table
answer 的替代方案:
library(data.table)
setDT(df.2)[df.1, on = "class", prob := i.prob][]
object class prob 1: A 2 0.7 2: B 1 0.5 3: D 2 0.7 4: F 3 0.3 5: C 1 0.5
在这里,df.2
与 df.1
右连接并获得一个新列 prob
,该列是从 df.1
的匹配行复制而来的。
【讨论】:
对于多列你可以做setDT(df.2)[df.1, on = "class", names(df.1) := mget(paste0("i.", names(df.1)))]
【参考方案9】:
在这种特殊情况下,您可以通过factor
获取紧凑型基础解决方案:
df.2$prob = factor(df.2$class,labels=df.1$prob)
df.2
# object class prob
# 1 A 2 0.7
# 2 B 1 0.5
# 3 D 2 0.7
# 4 F 3 0.3
# 5 C 1 0.5
然而,这不是一个通用的解决方案,它适用于:
-
您有一个包含唯一值的查找表
您要更新表,而不是创建新表
查找表按合并列排序
查找表没有额外的级别
你想要一个
left_join
如果你对因素没问题
1 不可商量,剩下的我们可以做:
df.3 <- df.2 # deal with 2.
df.1b <- df.1[order(df.1$class),] # deal with 3
df.1b <- df.1b[df.1$class %in% df.2$class,] # deal with 4.
df.3$prob = factor(df.3$class,labels=df.1b$prob)
df.3 <- df3[!is.na(df.3$prob),] # deal with 5. if you want an `inner join`
df.3$prob <- as.numeric(as.character(df.3$prob)) # deal with 6.
【讨论】:
【参考方案10】:有几个用例可以使用简单的子集:
# Use the key variable as row.names
row.names(df.1) = df.1$key
# Sort df.1 so that it's rows match df.2
df.3 = df.1[df.2$key, ]
# Create a data.frame with cariables from df.1 and (the sorted) df.2
df.4 = cbind(df.1, df.3)
此代码将保留 df.2 及其顺序并仅添加来自 df.1 的匹配数据
如果只添加一个变量,则不需要cbind()
:
row.names(df.1) = df.1$key
df.2$data = df.1[df.2$key, "data"]
【讨论】:
【参考方案11】:对于软件包开发人员
作为包开发人员,您希望尽可能少地依赖其他包。尤其是 tidyverse 函数,对于包开发人员恕我直言,这些函数变化太频繁了。
为了能够在不导入dplyr
的情况下使用dplyr
包的连接功能,下面是一个快速实现。它保持原来的排序(按照 OP 的要求)并且不会将连接列移到前面(这是merge()
的另一个烦人的事情)。
left_join <- function(x, y, ...)
merge_exec(x = x, y = y, all.x = TRUE, ...)
right_join <- function(x, y, ...)
merge_exec(x = x, y = y, all.y = TRUE, ...)
inner_join <- function(x, y, ...)
merge_exec(x = x, y = y, all = TRUE, ...)
full_join <- function(x, y, ...)
merge_exec(x = x, y = y, ...)
# workhorse:
merge_exec <- function(x, y, ...)
# set index
x$join_id_ <- 1:nrow(x)
# do the join
joined <- merge(x = x, y = y, sort = FALSE, ...)
# get suffices (yes, I prefer this over suffixes)
if ("suffixes" %in% names(list(...)))
suffixes <- list(...)$suffixes
else
suffixes <- c("", "")
# get columns names in right order, so the 'by' column won't be forced first
cols <- unique(c(colnames(x),
paste0(colnames(x), suffixes[1]),
colnames(y),
paste0(colnames(y), suffixes[2])))
# get the original row and column index
joined[order(joined$join_id),
cols[cols %in% colnames(joined) & cols != "join_id_"]]
【讨论】:
【参考方案12】:评分最高的答案不会产生原始海报想要的结果,即第 1 列中的“类”。如果 OP 允许在 df.2 中切换列顺序,那么这里是一个可能的基本 R 非合并之一-线路答案:
df.1 <- data.frame(class = c(1, 2, 3), prob = c(0.5, 0.7, 0.3))
df.2 <- data.frame(class = c(2, 1, 2, 3, 1), object = c('A', 'B', 'D', 'F', 'C'))
cbind(df.2, df.1[match(df.2$class, df.1$class), -1, drop = FALSE])
我碰巧喜欢 row.names 中描述的信息。完全复制 OP 期望结果的完整单行是
data.frame(cbind(df.2, df.1[match(df.2$class, df.1$class), -1, drop = FALSE]),
row.names = NULL)
我同意https://***.com/users/4575331/ms-berends 的观点,即一个包开发者对另一个包(或“诗句”)的依赖越少越好,因为随着时间的推移,开发路径经常会出现分歧。
注意:当df.1$class
中有重复项时,上面的单行代码不起作用。这可以通过'outer'
和循环的无合并来解决,或者更普遍地通过 Berend 女士的巧妙的合并后重新加扰代码来解决。
【讨论】:
以上是关于在保持原始行顺序的同时合并两个数据框的主要内容,如果未能解决你的问题,请参考以下文章
使用 isin() 时如何维护原始数据帧索引并保持输入列表的顺序?
如何在 Hive 中对文件进行重复数据删除并保持原始排序顺序?
【R】行或列数目不同的两个数据框如何用rbind/cbind合并?