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

7

我在尝试将两个相当大的数据集(但不是非常巨大,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

2
如果您提供数据结构(例如,head(dataframe)),也许您可以得到更好的答案。 - kohske
1
感谢您发布“head”的输出。另一个数据框的格式是什么(相同吗?)您期望结果中有哪些列? - NPE
1
在多个匹配项的情况下,您希望发生什么?而在没有匹配项的情况下呢? - Andrie
3
你在干什么?如果我合并两个300,000 x 4的数据框,使用merge函数大约需要占用0.15 Gb的内存。你可以通过确保你的工作空间没有被之前分析留下的垃圾文件占满来有效地合并两个数据集。或者购买超过1Gb内存的电脑。或者更新到最新的R版本。或者以上三者的组合。 - Joris Meys
2
@user702432:每个表格都有100万行,其中20万行不匹配,还有大约10万行是双倍、三倍或四倍匹配。尽管如此,我只使用了0.6 Gb的内存。所以我的问题又来了:你到底在干什么?顺便说一句,我确实读过文档。 - Joris Meys
显示剩余5条评论
3个回答

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")

使用两个包含500,000行和4列的数据框进行相同操作,每个学生姓名最多有10个匹配项,返回一个包含5.8百万行和8列的数据框,并在内存上呈现以下图片:

enter image description here

黄色框是合并调用,绿色框是mymerge调用。内存范围从2.3Gb到3.74Gb,因此merge调用使用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()

Joris,感谢您提供如此详细的回复。 - user702432

2

你尝试过data.table包吗?它更加节省内存,速度也可以快很多倍。但是,正如其他人所指出的,由于没有提供代码,因此有可能你只是错误地使用了merge。


1

我同意其他评论者的观点,认为这个问题在描述上有些不足(缺少代码和完整的数据描述),但我也想知道是否已经有以下链接中的一个回答了这个问题:

R:如何将两个巨大的数据框rbind在一起而不会耗尽内存

@G. Grothendieck提供的引用(他应该因为对R功能的许多贡献而被授予爵位),特别是关于使用外部文件的部分: http://code.google.com/p/sqldf/#Example_6._File_Input

最后一个想法:在保存工作、关闭计算机、仅重新启动R并加载数据集后,尝试像这样进行cbind(.... match(..))操作:

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

它可能没有合并操作那么多的功能,但如果问题仅仅是当前会话中的内存碎片,它应该相当节省内存,并且能够成功。这些不是部分匹配。如果您期望的是部分匹配,那么您应该明确指出。名称通常来自独立来源,因此可能会很混乱。


cbind 在这里会占用大量内存。当您有多个匹配项时,它无法正常工作。 - Joris Meys
没错。不幸的是,cbind 在这里行不通。 - user702432

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接