我有两个具有共享列的数据框(相同的列名称和类型),两个数据框具有相同数量的行(200K)和相同数量的列(172)。为了简洁起见,我将它们表示如下:
df1:
ID COL1 COL2
1 X
2 X
3 X
4 X
5 X
df2:
ID COL1 COL2
1 Y
2 Y
3 X
4 Y
5 Y
我想获得第三个数据框,以使我可以通过ID在2个数据框中的任何一个上保留 ALL 相关信息(意味着这两个常见的 AND 不常见的信息) 。对于此示例,结果应为:
df_result:
ID COL1 COL2
1 X Y
2 Y X
3 X
4 Y X
5 X Y
到目前为止,我已经使用dplyr::anti_join()
和dplyr::semi_join()
分离了常见值和不常见值,但是通过这种方法,以后将它们汇总到结果数据帧中比较麻烦,因为要比较的列太多了(由于结果基于column.x和column.y)。任何帮助表示赞赏。
答案 0 :(得分:3)
假设存在非NA值时,DF2
获胜,此解决方案将起作用:
DF3 <- DF1
DF3[!is.na(DF2)] <- DF2[!is.na(DF2)]
DF3
ID col1 col2
1 1 x y
2 2 y x
3 3 x <NA>
4 4 y x
5 5 x y
如果您以tibble
或data.table
开头,则可以将其更改为data.frame
DF3 <- as.data.frame(DF1)
DF3[!is.na(DF2)] <- as.data.frame(DF2)[!is.na(DF2)]
开箱即用的tibbles
两个基本选项是:
DF3 <- DF1
DF3[, names(DF3)[-1]] <- mapply(function(x,y) ifelse(is.na(x), y,x), DF1[, -1], DF2[, -1])
DF3[, names(DF3)[-1]] <- lapply(seq_len(length(DF1))[-1], function(i) ifelse(is.na(DF1[[i]]), DF2[[i]], DF1[[i]]))
#if you don't mind packages, ```dplyr::coalesce``` is faster and reads better:
library(dplyr)
DF3[, names(DF3)[-1]] <- mapply(coalesce, DF1[, -1], DF2[, -1])
DF3[, names(DF3)[-1]] <- lapply(seq_len(length(DF1))[-1], function(i) coalesce(DF1[[i]], DF2[[i]]))
# A tibble: 5 x 3
ID col1 col2
<int> <chr> <chr>
1 1 x y
2 2 y x
3 3 x NA
4 4 y x
5 5 x y
对于一个更健壮的选项,它可以使data.frames具有不同的行,我们可以使用data.table
update join:
library(data.table)
dt_1 <- as.data.table(DF1)
dt_2 <- as.data.table(DF2)
cols = names(dt_1)[-1]
dt_1[dt_2
, on = 'ID'
, (cols) := lapply(seq_along(cols), function(i) coalesce(get(cols[i]), get(paste0('i.', cols[i]))))
]
dt_1
ID col1 col2
1: 1 x y
2: 2 y x
3: 3 x <NA>
4: 4 y x
5: 5 x y
性能 我提出的所有基本选项都没有分组,这些分组应允许更好的向量化。
# Data repeated to have 50,000 rows
Unit: milliseconds
expr min lq mean median uq max neval
cole_base_df 46.1678 46.6577 47.79072 46.7874 47.8612 51.4795 5
cole_base_mapply 36.3574 38.0716 40.42820 39.5467 40.1889 47.9764 5
cole_base_lapply 27.3791 30.1052 31.30574 31.2388 33.0415 34.7641 5
# lapply with coalesce
cole_base_lapply 2.2017 2.2226 2.68914 2.2928 2.4140 4.3146 5
cole_dt 11.6885 12.2909 12.41180 12.5288 12.7141 12.8367 5
andrew_dplyr 7287.7865 7513.3745 7545.59520 7576.0932 7655.2974 7695.4244 5
andrew_dt 624.4604 647.1066 674.93512 689.3315 698.1462 715.6309 5
ronak_dplyr 9660.8393 9779.2466 10071.20714 10156.6727 10286.6954 10472.5817 5
ronak_base 10399.2761 10526.9840 10613.55536 10691.6657 10723.1021 10726.7489 5
# Data repeated to have 500 rows
Unit: microseconds
expr min lq mean median uq max neval
cole_base_df 570.1 674.70 719.660 706.75 726.95 2736.2 100
cole_base_mapply 580.8 640.75 696.913 671.35 695.75 2689.5 100
cole_base_lapply 424.8 460.40 517.155 492.85 518.90 3220.3 100
cole_dt 2645.5 3000.55 3120.355 3093.35 3167.45 5958.5 100
andrew_dplyr 73523.4 76009.45 78125.912 77151.25 78673.85 125830.3 100
andrew_dt 6777.3 7195.80 7644.179 7318.45 7579.15 11365.1 100
ronak_dplyr 94523.3 99039.50 102829.575 100026.15 101643.70 169167.3 100
ronak_base 93602.2 96086.30 97806.927 97470.05 98376.60 123348.8 100
数据:
DF1 <- data.frame(ID = seq_len(5)
,col1 = c('x', NA_character_, 'x', NA_character_, 'x')
,col2 = c(NA_character_, 'x', NA_character_, 'x', NA_character_)
, stringsAsFactors = F)
DF2 <- data.frame(ID = seq_len(5)
,col1 = c(NA_character_, 'y', 'x', 'y', NA_character_)
,col2 = c('y', NA_character_, NA_character_, NA_character_, 'y')
, stringsAsFactors = F)
为个人编写完整的代码以完成自己的基准测试
library(microbenchmark)
library(dplyr)
library(data.table)
DF1 <- data.frame(ID = seq_len(5)
,col1 = c('x', NA_character_, 'x', NA_character_, 'x')
,col2 = c(NA_character_, 'x', NA_character_, 'x', NA_character_)
, stringsAsFactors = F
)
DF2 <- data.frame(ID = seq_len(5)
,col1 = c(NA_character_, 'y', 'x', 'y', NA_character_)
,col2 = c('y', NA_character_, NA_character_, NA_character_, 'y')
, stringsAsFactors = F
)
n_rep <- 100 #change to 10000 if you want 50,000 rows)
DF1 <- do.call(rbind, replicate(n_rep, DF1, simplify = F))
DF1$ID <- seq_len(nrow(DF1))
DF2 <- do.call(rbind, replicate(n_rep, DF2, simplify = F))
DF2$ID <- seq_len(nrow(DF2))
dt_1 <- as.data.table(DF1)
dt_2 <- as.data.table(DF2)
microbenchmark(
cole_base_df = {
DF3 <- DF1
DF3[!is.na(DF2)] <- DF2[!is.na(DF2)]
}
,cole_base_mapply = {
DF3 <- DF1
DF3[, names(DF3)[-1]] <- mapply(function(x,y) ifelse(is.na(x), y,x), DF1[, -1], DF2[, -1])
# or better
# DF3[, names(DF3)[-1]] <- mapply(dplyr::coalesce, DF1[, -1], DF2[, -1])
}
,cole_base_lapply = {
DF3 <- DF1
DF3[, names(DF3)[-1]] <- lapply(seq_len(length(DF1))[-1], function(i) ifelse(is.na(DF1[[i]]), DF2[[i]], DF1[[i]]))
# or better
# DF3[, names(DF3)[-1]] <- lapply(seq_len(length(DF1))[-1], function(i) dplyr::coalesce(DF1[[i]], DF2[[i]]))
}
,cole_dt = {
cols = names(dt_1)[-1]
copy(dt_1)[copy(dt_2)
, on = 'ID'
, (cols) := lapply(seq_along(cols), function(i) coalesce(get(cols[i]), get(paste0('i.', cols[i]))))
][]
}
, andrew_dplyr = {
dplyr::union(DF1, DF2) %>%
group_by(ID) %>%
mutate_at(vars(starts_with("col")), ~ifelse(any(!is.na(.)), .[!is.na(.)], .)) %>%
distinct
}
, andrew_dt = {
rbindlist(list(DF1, DF2))[, lapply(.SD, function(x) ifelse(any(!is.na(x)), x[!is.na(x)], x)), by = "ID"]
}
, ronak_dplyr = {
bind_rows(DF1, DF2) %>%
group_by(ID) %>%
summarise_at(vars(starts_with("col")), ~toString(na.omit(unique(.))))
}
, ronak_base = {
aggregate(.~ID, rbind(DF1, DF2),
function(x) toString(na.omit(unique(x))), na.action = "na.pass")
}
, times = 5
)
答案 1 :(得分:3)
这里是一个dplyr
解决方案,应该灵活(注意:像Ronak Shah这样使用summarise_at
可能比mutate_at
+ distinct
更有效):< / p>
library(dplyr)
dplyr::union(df1, df2) %>%
group_by(ID) %>%
mutate_at(vars(starts_with("COL")), ~ifelse(any(!is.na(.)), .[!is.na(.)], .)) %>%
distinct
ID COL1 COL2
<chr> <chr> <chr>
1 1 X Y
2 2 Y X
3 3 X NA
4 4 Y X
5 5 X Y
或者,在data.table
上使用相同的逻辑:
library(data.table)
setDT(rbind(df1, df2))[, lapply(.SD, function(x) ifelse(any(!is.na(x)), x[!is.na(x)], x)), by = "ID"]
数据(注意,我在您有空白单元格的地方添加了NA
)
df1 <- read.table(header = T, text = "ID COL1 COL2
1 X NA
2 NA X
3 X NA
4 NA X
5 X NA")
df2 <- read.table(header = T, text = "ID COL1 COL2
1 NA Y
2 Y NA
3 X NA
4 Y NA
5 NA Y")
答案 2 :(得分:2)
这里是另一个使用dplyr
的版本,它将两个数据帧绑定在一起,group_by
ID
和paste
所有unique
的值在一起。
library(dplyr)
bind_rows(df1, df2) %>%
group_by(ID) %>%
summarise_at(vars(starts_with("COL")), ~toString(na.omit(unique(.))))
# ID COL1 COL2
# <chr> <chr> <chr>
#1 1 X Y
#2 2 Y X
#3 3 X ""
#4 4 Y X
#5 5 X Y
以及类似的在基数R中使用aggregate
的情况
aggregate(.~ID, rbind(df1, df2),
function(x) toString(na.omit(unique(x))), na.action = "na.pass")