77

我想合并两个数据框,保持其中一个的原始行顺序(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
4

12 回答 12

62

您只需要创建一个变量,它给出 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), ]
于 2013-07-26T09:54:48.570 回答
46

查看 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-合并-td4296561.html

于 2013-07-30T18:31:35.297 回答
15

您还可以查看inner_joinHadleydplyr包中的函数(下一次迭代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
于 2014-07-18T07:51:18.517 回答
11

data.table v1.9.5+,您可以:

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

通过为每一行class找出匹配的行并提取相应的列,对列执行连接。df.1df.2

于 2015-08-20T17:33:31.263 回答
5

为了完整起见,连接中的更新也保留了原始行顺序。如果只有几列要附加,这可能是Arundata.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.2右连接df.1并获得一个prob从 的匹配行复制的新列df.1

于 2018-02-01T08:14:02.893 回答
3

接受的答案提出了一种在使用时保持秩序的手动方法,该方法merge在大多数情况下都有效,但需要不必要的手动工作。该解决方案位于How to ddply() withoutsorting?,它处理保持顺序的问题,但在拆分-应用-组合上下文中:

这在 plyr 邮件列表上出现了一段时间(由@kohske 提出),这是 Peter Meil​​strup 为有限情况提供的解决方案:

#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有效地自动化了已接受答案中的方法。

于 2014-09-23T15:59:37.443 回答
2

感谢@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
}

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

于 2013-08-09T13:35:28.337 回答
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.
于 2018-02-26T19:23:16.373 回答
1

对于包开发人员

作为包开发人员,您希望尽可能少地依赖其他包。尤其是 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_"]]
}
于 2020-05-02T13:48:00.590 回答
1

评分最高的答案不会产生原始海报想要的结果,即第 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://stackoverflow.com/users/4575331/ms-berends的观点,即一个包开发者对另一个包(或“诗句”)的依赖越少越好,因为开发路径经常随着时间的推移而分道扬镳。

注意:当df.1$class. 这可以在没有合并'outer'和循环的情况下克服,或者更普遍地使用 Berend 女士巧妙的合并后重新加扰代码。

于 2020-06-15T18:50:10.057 回答
0

有几个用例可以使用一个简单的子集:

# 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()不需要 ist:

row.names(df.1) = df.1$key
df.2$data = df.1[df.2$key, "data"]
于 2019-02-20T11:49:25.113 回答
-1

在 base 中可能有更有效的方法。这将是相当简单的功能。

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

mydata[c(varorder,restOfvars)]
于 2015-08-20T17:25:42.487 回答