在保持原始行顺序的同时合并两个数据帧

我想合并两个数据帧,保持其中一个的原始行顺序(下面例子中的 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
98851 次浏览

您只需要创建一个变量,它给出 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), ]

查看 plyr 包中的 join 函数。它类似于 merge,但是它允许您保持其中一个数据集的行顺序。总的来说,它比合并更灵活。

使用您的示例数据,我们将像下面这样使用 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

这里有几个链接,描述了为保持行顺序而对 merge 函数进行的修复:

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

多亏了@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
}

这假设您希望保留第一个数据帧的顺序,并且合并的数据帧将具有与第一个数据帧相同的行数。它将为您提供干净的数据框架,而不需要额外的列。

您还可以查看 Hadley 的 dplyr包(plyr的下一个迭代)中的 inner_join函数。它保留第一个数据集的行顺序。与所期望的解决方案稍有不同的是,它还保留了第一个数据集的原始列顺序。所以它不一定把我们用来合并的列放在第一个位置。

使用上面的例子,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

在使用 merge时,接受的答案提出了一种手动的方法来保持秩序,这种方法在大多数情况下是有效的,但是需要不必要的手动操作。这个解决方案是在 如何在不排序的情况下进行 ddly () ?的基础上提出来的,如何在不排序的情况下进行 ddly () ?处理维持秩序的问题,但是是在一个拆分-应用-合并的上下文中:

这是前不久在 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在接受的答案中有效地自动化了该方法。

也许有一个更有效的方法在基地。这将是相当简单的作为一个函数。

varorder <- names(mydata)  # --- Merge
mydata <- merge(mydata, otherData, by="commonVar")
restOfvars <- names(mydata[!(names(mydata) %in% varorder)])


mydata[c(varorder,restOfvars)]

Table v1.9.5 + ,你可以做到:

require(data.table) # v1.9.5+
setDT(df.1)[df.2, on="class"]

它通过为 df.2中的每一行在 df.1中找到匹配的行并提取相应的列来对列 class执行连接。

为了完整起见,在联接中更新也保留了原始的行顺序。如果只需要附加几个列,这可能是 阿伦的 data.table答案的替代方案:

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.2df.1直接连接并获得一个新的列 prob,该列是从 df.1的匹配行复制的。

在这种情况下,你可以我们 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

然而,这并不是一个普遍的解决方案,只有在下列情况下才能奏效:

  1. 您有一个包含唯一值的查找表
  2. 您希望更新表,而不是创建新表
  3. 查找表按合并列排序
  4. 查找表没有额外的级别
  5. 你需要 left_join
  6. 如果你不介意的话

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.

在一些用例中,一个简单的子集可以完成:

# 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"]

对于软件包开发人员

作为软件包开发人员,您希望尽可能少地依赖其他软件包。特别是整洁的功能,这变化的方式太经常为软件包开发人员。

为了能够在不导入 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_"]]
}

评分最高的答案不会产生原始海报所希望的结果,即第1栏中的“ class”。如果 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])

我碰巧喜欢行名中描述的信息。完全复制 OP 所期望的结果的完整的一行程序是

data.frame(cbind(df.2, df.1[match(df.2$class, df.1$class), -1, drop = FALSE]),
row.names = NULL)

我同意 https://stackoverflow.com/users/4575331/ms-berends的观点,即一个包开发人员对另一个包的依赖性越少(或“韵律”)越好,因为开发路径经常随着时间的推移而发生分歧。

注意: 当 df.1$class中有重复内容时,上面的一行程序不起作用。这可以克服与 'outer'合并和循环,或更一般与贝伦德女士的聪明的合并后重新加密代码。