利用相似数据帧的内容提高更新大数据帧内容的性能

时间:2012-04-04 16:37:54

标签: r dataframe data.table

我正在寻找使用第二个类似数据帧的内容更新一个大数据帧的通用解决方案。我有几十个数据集,每个数据集有数千行,超过10,000列。 “更新”数据集将与其对应的“基础”数据集重叠,从几个百分点到大约百分之五十,行方向。数据集具有“键”列,并且在任何给定数据集中每个唯一键值只有一行。

基本规则是:如果给定单元格的更新数据集中存在非NA值,请使用该值替换基础数据集中的相同单元格。 (“相同单元格”表示“key”列和colname的相同值。)

请注意,更新数据集可能包含我可以使用rbind处理的新行(“插入”)。

所以给定基础数据框“df1”,其中列“K”是唯一的键列,“P1”。“P3”代表10,000列,其名称将从一对数据集到下一个数据集不等:

  K P1 P2 P3
1 A  1  1  1
2 B  1  1  1
3 C  1  1  1

...和更新数据框“df2”:

  K P1 P2 P3
1 B  2 NA  2
2 C NA  2  2
3 D  2  2  2

我需要的结果如下,其中“B”和“C”的1被2覆盖但未被NA覆盖:

  K P1 P2 P3
1 A  1  1  1
2 B  2  1  2
3 C  1  2  2
4 D  2  2  2

这似乎不是合并候选者,因为合并给我重复行(相对于“键”列)或重复列(例如P1.x,P1.y),我必须迭代以某种方式崩溃。

我尝试过预先分配一个带有最终行/列尺寸的矩阵,然后用df1的内容填充它,然后遍历重叠的df2行,但是我不能比每秒20个单元的性能更好,需要数小时才能完成(与SAS中等效的DATA step UPDATE功能的分钟数相比)。

我确定我错过了一些东西,但找不到类似的例子。

我看到ddply用法看起来很接近,但不是一般解决方案。 data.table包似乎没有帮助,因为我不明白这是一个连接问题,至少通常没有这么多列。

同样只关注交叉行的解决方案就足够了,因为我可以识别其他行并将它们装入。

以下是制作上述数据框的一些代码:

cat("K,P1,P2,P3", "A,1,1,1", "B,1,1,1", "C,1,1,1", file="f1.dat", sep="\n");
cat("K,P1,P2,P3", "B,2,,2", "C,,2,2", "D,2,2,2", file="f2.dat", sep="\n");
df1 <- read.table("f1.dat", sep=",", header=TRUE, stringsAsFactors=FALSE);
df2 <- read.table("f2.dat", sep=",", header=TRUE, stringsAsFactors=FALSE);

由于

4 个答案:

答案 0 :(得分:2)

这可能不是最快的解决方案,但完全是在基础上完成的。

(根据Tommy的评论更新答案)

#READING IN YOUR DATA FRAMES
df1 <- read.table(text="  K P1 P2 P3
1 A  1  1  1
2 B  1  1  1
3 C  1  1  1", header=TRUE)

df2 <- read.table(text="  K P1 P2 P3
1 B  2 NA  2
2 C NA  2  2
3 D  2  2  2", header=TRUE)

all <- c(levels(df1$K), levels(df2$K))                  #all cells of key column
dups <- all[duplicated(all)]                            #the overlapping key cells
ndups <- all[!all %in% dups]                            #unique key cells
df3 <- rbind(df1[df1$K%in%ndups, ], df2[df2$K%in%ndups, ]) #bind the unique rows

decider <- function(x, y) ifelse(is.na(x), y, x) #function replaces NAs if existing
df4 <- data.frame(mapply(df2[df2$K%in%dups, ], df1[df1$K%in%dups, ], 
    FUN = decider)) #repalce all NAs of df2 with df1 values if they exist

df5 <- rbind(df3, df4) #bind unique rows of df1 and df2 with NA replaced df4
df5 <- df5[order(df5$K), ]  #reorder based on key column
rownames(df5) <- 1:nrow(df5)  #give proper non duplicated rownames
df5

这会产生:

  K P1 P2 P3
1 A  1  1  1
2 B  2  1  2
3 C  1  2  2
4 D  2  2  2

仔细阅读并非所有列都具有相同的名称,但我假设订单相同。这可能是一种更有帮助的方法:

all <- c(levels(df1$K), levels(df2$K))
dups <- all[duplicated(all)]
ndups <- all[!all %in% dups]
LS <- list(df1, df2)
LS2 <- lapply(seq_along(LS), function(i) {
        colnames(LS[[i]]) <- colnames(LS[[2]])
        return(LS[[i]])
    }
)

LS3 <- lapply(seq_along(LS2), function(i) LS2[[i]][LS2[[i]]$K%in%ndups, ])
LS4 <- lapply(seq_along(LS2), function(i) LS2[[i]][LS2[[i]]$K%in%dups, ])

decider <- function(x, y) ifelse(is.na(x), y, x)
DF <- data.frame(mapply(LS4[[2]], LS4[[1]], FUN = decider))
DF$K <- LS4[[1]]$K
LS3[[3]] <- DF
df5 <- do.call("rbind", LS3)
df5 <- df5[order(df5$K), ]
rownames(df5) <- 1:nrow(df5)
df5

答案 1 :(得分:1)

编辑:请忽略此答案。一个循环的坏主意。它工作但很慢。留给子孙后代!将我的第二次尝试视为单独的答案。

require(data.table)
dt1 = as.data.table(df1)
dt2 = as.data.table(df2)
K = dt2[[1]]
for (i in 1:nrow(dt2)) {
    k = K[i]
    p = unlist(dt2[i,-1,with=FALSE])
    p = p[!is.na(p)]
    dt1[J(k),names(p):=as.list(p),with=FALSE]
}

或者,您可以使用matrix代替data.frame吗?如果是这样,它可以是使用A[B]语法的单行,其中B是一个包含要更新的行号和列号的2列矩阵。

答案 2 :(得分:1)

这是按列循环,通过引用设置dt1并且(希望)应该很快。

dt1 = as.data.table(df1)
dt2 = as.data.table(df2)
if (!identical(names(dt1),names(dt2)))
    stop("Assumed for now. Can relax later if needed.")
w = chmatch(dt2$K, dt1$K)
for (i in 2:ncol(dt2)) {
    nna = !is.na(dt2[[i]])
    set(dt1,w[nna],i,dt2[[i]][nna])
}
dt1 = rbind(dt1,dt2[is.na(w)])
dt1
     K P1 P2 P3
[1,] A  1  1  1
[2,] B  2  1  2
[3,] C  1  2  2
[4,] D  2  2  2

答案 3 :(得分:0)

以下给出了小示例数据的正确答案,尝试最小化表的“副本”数量,并使用新的fread和(new?)rbindlist。它适用于较大的实际数据集吗?我没有完全遵循原帖中关于你在尝试压缩/标准化/堆叠时遇到的内存问题的所有评论,所以如果你已经尝试过这条路线,那么道歉。

library(data.table)
library(reshape2)

cat("K,P1,P2,P3", "A,1,1,1", "B,1,1,1", "C,1,1,1", file="f1.dat", sep="\n")
cat("K,P1,P2,P3", "B,2,,2", "C,,2,2", "D,2,2,2", file="f2.dat", sep="\n")

dt1s<-data.table(melt(fread("f1.dat"), id.vars="K"), key=c("K","variable")) # read f1.dat, melt to long/stacked format, and convert to data.table

dt2s<-data.table(melt(fread("f2.dat"), id.vars="K", na.rm=T), key=c("K","variable")) # read f2.dat, melt to long/stacked format (removing NAs), and convert to data.table
setnames(dt2s,"value","value.new")

dt1s[dt2s,value:=value.new] # Update new values

dtout<-reshape(rbindlist(list(dt1s,dt1s[dt2s][is.na(value),list(K,variable,value=value.new)])), direction="wide", idvar="K", timevar="variable") # Use rbindlist to insert new records, and then reshape
setkey(dtout,K)
setnames(dtout,colnames(dtout),sub("value.", "", colnames(dtout))) # Clean up the column names