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

R 利用相似数据帧的内容提高大数据帧内容更新性能,r,dataframe,data.table,R,Dataframe,Data.table,我正在寻找一个通用的解决方案,用第二个类似数据帧的内容更新一个大数据帧。我有几十个数据集,每个数据集有数千行和超过10000列。“更新”数据集将与相应的“基础”数据集重叠几%到50%,按行排列。数据集有一个“键”列,在任何给定的数据集中,每个唯一的键值只有一行 基本规则是:如果给定单元格的更新数据集中存在非NA值,则用该值替换基本数据集中的同一单元格。(相同单元格表示“key”列和colname的相同值。) 注意,更新数据集可能包含我可以用rbind处理的新行(“插入”) 因此,给定基本数据帧“

我正在寻找一个通用的解决方案,用第二个类似数据帧的内容更新一个大数据帧。我有几十个数据集,每个数据集有数千行和超过10000列。“更新”数据集将与相应的“基础”数据集重叠几%到50%,按行排列。数据集有一个“键”列,在任何给定的数据集中,每个唯一的键值只有一行

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

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

因此,给定基本数据帧“df1”,其中列“K”是唯一的键列,“P1”。。“P3”表示10000列,每对数据集的名称各不相同:

  K P1 P2 P3
1 A  1  1  1
2 B  1  1  1
3 C  1  1  1
…和更新数据帧“df2”:

我需要的结果如下,其中“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
这似乎不是一个合并候选项,因为merge给了我重复的行(相对于“key”列)或重复的列(例如P1.x、P1.y),我必须迭代才能以某种方式折叠它们

我已经尝试过用最终行/列的维度预先分配一个矩阵,并用df1的内容填充它,然后在df2的重叠行上迭代,但我的性能不能超过每秒20个单元格,需要数小时才能完成(相比之下,SAS中的等效数据步更新功能需要数分钟).

我肯定我遗漏了一些东西,但找不到类似的例子

我看到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”);
目录(“K,P1,P2,P3”,“B,2,2”,“C,2,2”,“D,2,2,2”,file=“f2.dat”,sep=“\n”);

df1这可能不是最快的解决方案,但完全在base中完成

(根据汤米的评论更新答案)

仔细阅读后,并非所有列都有相同的名称,但我假设顺序相同。这可能是一种更有用的方法:

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

all编辑:请忽略此答案。按行循环是个坏主意。它可以工作,但速度很慢。留给子孙后代!将我的第二次尝试作为单独的答案

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]
}

或者,您可以使用
矩阵
而不是
数据帧
?如果是这样的话,它可以是一行,使用
a[B]
语法,其中
B
是一个两列矩阵,包含要更新的行号和列号。

这将按列循环,通过引用设置
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

下面给出了小示例数据的正确答案,尝试最小化表的“副本”数量,并使用新的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
库(data.table)
图书馆(E2)
类别(“K,P1,P2,P3”,“A,1,1,1”,“B,1,1,1”,“C,1,1,1”,file=“f1.dat”,sep=“\n”)
目录(“K,P1,P2,P3”,“B,2,2”,“C,2,2”,“D,2,2,2”,file=“f2.dat”,sep=“\n”)

DT1可能重复:我收回这不是重复。我读得不够仔细。您需要一个合并,将一个df的NAs替换为另一个df。更复杂一点。在
data.table
中,一种方法是将
df1
df2
展平为3列:(K,P,val),每个列都有一个2列键(K,P)。然后
df1[df2,val:=df2.val]
并随后取消设置。或者,保持您拥有的相同结构,在循环中通过
df2
do
df1[k,p:=value,with=FALSE]
这将很快,因为在
数据上的循环。表
s要快得多。如果您喜欢循环方法,那么
set()
甚至比
:=
@MatthewDowle更快使用
df1[df2,val:=df2.val]
的标准化(扁平化)路由只在j中使用;i、 例如,DT[i,col:=1L]不是DT[i,col]:=1L或DT[i]$col:=1L。
df1
需要是一个
数据表。
;e、 g.
df1=as.data.table(df1)
。我将在错误消息中添加一些内容,建议检查该类型……那么这如何处理键列匹配呢?看来所有的数值都变成了因子…@Tommy我更新了以反映你的因子评论。那是因为我刚刚剪切和粘贴了上次我给出的类似答案。@Tommy我现在也得到了匹配的键列。我以前没听说过。回到绘图板上。我认为这两个数据帧是相同的观察结果。@Tylerlinker可以假设列具有相同的名称,并且顺序相同;我有一些快速的预处理来确保这一点。如果所有文件都有相同的名称,那么我的第一种方法可能更容易。我不知道这对速度有多公平。一旦你测试了它,让我知道。这失败了,因为[.data.table(p,!is.na(p))中有
错误:我是无效类型(矩阵)。也许将来一个2列矩阵可以返回一个DT元素列表(按照FAQ 2.14中a[B]的精神)。
Re:the
a[B]
语法,如果我需要用更新指令构造
B
,那么我必须遍历所有单元格以确定是否符合目的,除非
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]
}
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
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