如何有效地合并两个数据集?

时间:2011-09-16 06:58:09

标签: r memory memory-management merge dataframe

我试图合并两个相当大的 - 但并非荒谬(360,000 X 4,57,000 X 4) - 一个公共ID的数据集。我尝试过常规merge()merge.data.table()sqldf()。每次我内存不足(cannot allocate vector of size...)。这有什么解决方案吗?或者R是合并数据的坏工具吗?下面给出head()(我想在STUDENT.NAME上合并):

  ID10    STUDENT.NAME   FATHER.NAME MOTHER.NAME
1    1     DEEKSHITH J       JAYANNA      SWARNA
2    4    MANIKANTHA D       DEVARAJ     MANJULA
3    5        NAGESH T   THIMMAIAH N    SHIVAMMA
4    6    NIZAMUDDIN R NOOR MOHAMMED        BIBI
5    7 PRABHU YELLAPPA      YELLAPPA    MALLAMMA
6    8    SADDAM PASHA   NISAR AHMED     ZAREENA

3 个答案:

答案 0 :(得分:11)

从问题的本质来看,您必须进行多对多合并,每个学生在每个数据框中都会多次出现。您可能想要查看多少次。如果每个学生在每个数据框中出现两次,这意味着一个学生将产生4行。如果学生出现10次,合并将增加100行。首先检查你会得到多少行。这是我用于此的功能:

count.rows <- function(x,y,v,all=FALSE){
    tx <- table(x[[v]])
    ty <- table(y[[v]])
    val <- val <- names(tx)[match(names(tx),names(ty),0L) > 0L]
    cts <- rbind(tx[match(val,names(tx))],ty[match(val,names(ty))])
    colnames(cts) <- val
    sum(apply(cts,2,prod,na.rm=all),na.rm=TRUE)
}
count.rows(DF1,DF2,"STUDENT.NAME")

如果您按照自己的要求行事(阅读R文档),您会发现复杂性取决于答案的长度。这不是由于合并算法本身,而是将所有结果绑定在一起。如果你真的想要一个内存较少的解决方案,你需要特别摆脱这种绑定。以下算法为您做到了这一点。我把它写出来,你可以找到逻辑,它可以被优化。请注意,它不会给出相同的结果,它会复制两个数据帧的所有列。所以你可能想稍微调整一下。

mymerge <- function(x,y,v,count.only=FALSE){
    ix <- match(v,names(x))
    iy <- match(v,names(y))

    xx <- x[,ix]
    yy <- y[,iy]
    ox <- order(xx)
    oy <- order(yy)
    xx <- xx[ox]
    yy <- yy[oy]

    nx <- length(xx)
    ny <- length(yy)

    val <- unique(xx)
    val <- val[match(val,yy,0L) > 0L]
    cts <- cbind(table(xx)[val],table(yy)[val])
    dimr <- sum(apply(cts,1,prod),na.rm=TRUE)

    idx <- vector("numeric",dimr)
    idy <- vector("numeric",dimr)
    ndx <- embed(c(which(!duplicated(xx)),nx+1),2)[unique(xx) %in% val,]
    ndy <- embed(c(which(!duplicated(yy)),ny+1),2)[unique(yy) %in% val,]

    count = 1
    for(i in 1:nrow(ndx)){
        nx <- abs(diff(ndx[i,]))
        ny <- abs(diff(ndy[i,]))
        ll <- nx*ny

        idx[count:(count+ll-1)] <-
          rep(ndx[i,2]:(ndx[i,1]-1),ny)

        idy[count:(count+ll-1)] <-
          rep(ndy[i,2]:(ndy[i,1]-1),each=nx)
        count <- count+ll
    }
    x <- x[ox[idx],]
    names(y) <- paste("y.",names(y),sep="")
    x[names(y)] <- y[oy[idy],]
    rownames(x) <- 1:nrow(x)
    x
}

一些测试代码,以便您可以看到它的工作原理:

DF1 <- data.frame(
    ID = 1:10,
    STUDENT.NAME=letters[1:10],
    SCORE = 1:10
)
id <- c(3,11,4,6,6,12,1,4,7,10,5,3)
DF2 <- data.frame(
    ID = id,
    STUDENT.NAME=letters[id],
    SCORE = 1:12
)

mymerge(DF1,DF2,"STUDENT.NAME")

对两个50万行和4列的数据帧进行相同操作,每个学生姓名最多10个匹配,它返回一个包含580万行和8列的数据帧,并在内存中显示以下图片:

enter image description here

黄色框是合并调用,绿色框是mymerge调用。内存范围从2.3Gb到3.74Gb,因此合并调用使用1.45 Gb,mymerge使用超过0.8 Gb。仍然没有“内存不足”错误......测试代码如下:

Names <- sapply(
      replicate(120000,sample(letters,4,TRUE),simplify=FALSE),
      paste,collapse="")

DF1 <- data.frame(
    ID10 = 1:500000,
    STUDENT.NAME = sample(Names[1:50000],500000,TRUE),
    FATHER.NAME = sample(letters,500000,TRUE),
    SCORE1 = rnorm(500000),
    stringsAsFactors=FALSE
)

id <- sample(500000,replace=TRUE)
DF2 <- data.frame(
    ID20 = DF1$ID10,
    STUDENT.NAME = DF1$STUDENT.NAME[id],
    SCORE = rnorm(500000),
    SCORE2= rnorm(500000),
    stringsAsFactors=FALSE
)
id2 <- sample(500000,20000)
DF2$STUDENT.NAME[id2] <- sample(Names[100001:120000],20000,TRUE)

gc()
system.time(X <- merge(DF1,DF2,"STUDENT.NAME"))
Sys.sleep(1)
gc()
Sys.sleep(1)
rm(X)
gc()
Sys.sleep(3)
system.time(X <- mymerge(DF1,DF2,"STUDENT.NAME"))
Sys.sleep(1)
gc()
rm(X)
gc()

答案 1 :(得分:2)

您是否尝试过data.table套餐?它的内存效率更高,速度可提高很多倍。但是,正如其他人所说,这个问题没有提供代码,所以你可能只是错误地使用了合并。

答案 2 :(得分:1)

我同意其他评论员的观点,他们说这个问题在描述中缺少一些(缺少代码和完整的数据描述),但我也想知道是否还没有用这些链接回答:

R: how to rbind two huge data-frames without running out of memory

@G提供的引文。格洛腾迪克(可能应该因为他对R的功能做出的许多贡献而获得骑士勋章),特别是有关使用外部文件的部分: http://code.google.com/p/sqldf/#Example_6._File_Input

最后一个想法:在保存你的工作后,关闭你的计算机,仅用R进行重建并只加载你的数据集,试试这样的cbind(.... match(..))机动:

cbind(df1,df2[match(df1$STUDENT.NAME,df2$STUDENT.NAME)),])

它不会像合并一样具有相同的功能,但它应该具有相当的内存效率并且如果问题只是当前会话中的碎片内存就会成功。这些不是部分匹配。如果这是你的期望,你应该表明这一点。如果来自独立来源,名字就是众所周知的。