【问题标题】:Improving performance of updating contents of large data frame using contents of similar data frame使用相似数据帧的内容来提高更新大数据帧内容的性能
【发布时间】:2012-04-18 10:08:02
【问题描述】:

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

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

请注意,更新数据集可能包含我可以使用 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 步 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);

谢谢

【问题讨论】:

  • 我收回这不是重复的。我读得不够仔细。您希望将一个 df 的 NA 替换为另一个 df 进行合并。有点复杂。
  • data.table 中,一种方法是将df1df2 展平为3 列:(K,P,val),每列都有一个2 列键(K,P)。然后df1[df2,val:=df2.val] 然后展开。或者,保持相同的结构,通过df2 循环执行df1[k,p:=value,with=FALSE],这将很快,因为data.tables 上的循环要快得多。如果您喜欢循环方法,那么set() 甚至比:= 更快。
  • @MatthewDowle 带有df1[df2,val:=df2.val] 的标准化(扁平化)路由给出Error in := (val, df2.val) : := is defined for use in j only; i.e., DT[i,col:=1L] not DT[i,col]:=1L or DT[i]$col:=1L.
  • df1 必须是data.table;例如df1=as.data.table(df1)。我将在该错误消息中添加一些内容以建议检查该类型。

标签: r dataframe data.table


【解决方案1】:

下面给出了小示例数据的正确答案,尽量减少表的“副本”数量,并使用新的 fread 和(新的?) rbindlist。它适用于您更大的实际数据集吗?我没有完全按照原始帖子中的所有 cmets 来了解您在尝试展平/标准化/堆栈时遇到的内存问题,因此,如果您已经尝试过这条路线,我们深表歉意。

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

【讨论】:

    【解决方案2】:

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

    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 列矩阵。

    【讨论】:

    • 这失败了 Error in [.data.table(p, !is.na(p)) : i is invalid type (matrix). Perhaps in future a 2 column matrix could return a list of elements of DT (in the spirit of A[B] in FAQ 2.14). 回复:A[B] 语法,如果我需要使用更新指令构造 B,那么我必须遍历所有单元格来确定,这会破坏目的,除非我误解了。
    • @gkaupas Apols,意思是unlist,而不是as.vector。已编辑答案。在A[B] 上,您很可能会矢量化B 的创建。如果 matrix 适合您的情况,那么(所有列整数或所有列数字,并且 n*m matrix 和 A[B] 似乎更合适并且应该更快。
    • 这会将错误向下移动到循环中的最后一行; Supplied n items to be assigned to 1 items of column 'P1' (n-1 unused) 形式的东西。我的数据将是字符串和数字的混合,所以我猜这意味着这里没有 matrix 路由。
    • @gkaupas Apols 再次编辑答案以将 names(p):=p 更改为 names(p):=as.list(p)。好的,是的,这意味着没有matrix 路由。必须是 data.table 中更简单的方法(也许通过将其包装在执行该任务的一些通用函数中)。
    • 好吧,我在顶部的 3 部分评论中发布的 dt1[dt2,decider(NEWVAL,OLDVAL)] 根据您的输入和 Tyler 的意见,非常简单,即使它需要我先规范化我的数据,并且只处理行的子集(即我可以取回 dt1 的所有行或 dt2 的所有行,并且必须在其余部分中进行 rbind)。不幸的是,它无法扩展到我的数据量。
    【解决方案3】:

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

    【讨论】:

    • 现在我们正在做饭。对于 1000 行数据集,我将每秒 3,200,000 个单元的“合并”速率增加到 10,000 行数据集每秒 7,200,000 个单元,该速率似乎与数据集中的列数无关。 然而代码似乎只有当值是数字时才表现。我的测试用例是所有的列都填充了 A 和 B 的字符,用data.table(read.table("f1.dat", sep=",", header=TRUE, stringsAsFactors=FALSE), key=c("K")) 读入,结果是所有dt1 显然被所有dt2 取代。什么可能导致这种区别?
    • 唷,听起来很有希望。没有理由为什么字符不应该与数字一样工作,afaik。需要一个例子,请展示你得到的和你期望的。疯狂猜测这是NA_character_"NA" 之间的区别?
    • 我的错,你的猜测确实是原因;在我的 read.table 中,我忘记指定 na.strings=""。添加后,您的代码就可以正常工作。示例here 其中dt2 包含2 次更新,第一个记录覆盖dt1 中的相同记录,第二个具有两列NA(一个是数字,一个是字符),并且它正确地对dt1 中的匹配记录没有影响。我将重新运行我的基准测试,其中dt2 有随机 NA,read.table 有正确选项,但这个解决方案显然是最有效的解决方案。感谢您的勤奋。
    • 救济。谢谢你的耐心,终于到了。事实证明,这对我也很有用。
    • 结语:尽管在我的硬件上每秒处理超过 300 万个单元格,但不幸的是,我的整体吞吐量受到读取/写入数据的限制。无论我是读/写 CSV 文件,还是加载/保存未压缩的 .RData 文件,在我用于 R 基准测试的服务器上,我的有效处理速率下降到 250K 单元/秒,或者在运行 SAS 的硬件上下降到大约 120K 单元/秒。这些数字仅用于读/写或加载/保存,两者之间没有合并代码。 SAS 以每秒 600K 单元的净速度读取/合并/写入。福。
    【解决方案4】:

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

    (根据 Tommy's cmets 更新答案)

    #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
    

    【讨论】:

    • ...那么这如何处理键列匹配?而且似乎所有数值都变成了因子......
    • @Tommy 我更新以反映您的因素评论。那是因为我只是从上次给出类似的分析器开始剪切和粘贴。
    • @Tommy 我现在也得到了键列匹配。之前没抓到。回到绘图板。我认为这 2 个数据框是相同的观察结果。
    • @TylerRinker 假设列将具有相同的名称并按相同的顺序是可以的;我有一些快速的预处理来确保这一点。
    • 如果所有文件都具有相同的名称,那么我的第一种方法可能更容易。不知道速度如何公平。测试后告诉我。
    猜你喜欢
    • 1970-01-01
    • 2011-01-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多