【问题标题】:merging multiple dataframes with duplicate rows in R将多个数据框与R中的重复行合并
【发布时间】:2016-06-21 18:11:45
【问题描述】:

对于这类事情,R 相对较新,搜索了很多,但找不到太多有用的东西。

我有大约 150 个 .csv 文件,每个文件有 40,000 - 60,000 行,我正在尝试将每个 3 列合并为 1 个大数据框。我有一个小脚本,它从每个文件中提取感兴趣的 3 列(“id”、“name”和“value”),并通过“id”和“name”与较大的数据框“MergedData”合并。这是我的代码(我确信这是一种非常低效的方法,我现在还可以,但我当然愿意接受更好的选择!):

file_list <- list.files()

for (file in file_list){

  if(!exists("MergedData")){
    MergedData <- read.csv(file, skip=5)[ ,c("id", "name", "value")]
    colnames(MergedData) <- c("id", "name", file)
  }

  else if(exists("MergedData")){
    temp_data <- read.csv(file, skip=5)[ ,c("id", "name", "value")]
    colnames(temp_data) <- c("id", "name", file)
    MergedData <- merge(MergedData, temp_data, by=c("id", "name"), all=TRUE)
    rm(temp_data)
  }
}

并非每个文件都具有相同的行数,尽管许多行对许多文件是公用的。我没有包含行的列表,因此我包含了 all=TRUE 以附加 MergedData 文件中尚不存在的新行。

我的问题是:许多文件包含 2-4 行,具有相同的“id”和“name”条目,但不同的“value”条目。因此,当我合并它们时,我最终会为每个可能的组合添加行,这很快就会失控。最令人沮丧的是,我对这些副本都没有任何兴趣。有没有一种简单的方法来获取第一个条目的值并忽略任何进一步的重复条目?

谢谢!

【问题讨论】:

    标签: r dataframe merge


    【解决方案1】:

    根据您的评论,我们可以堆叠每个文件,然后将生成的数据帧从“长”格式转换为“宽”格式:

    library(dplyr)
    library(readr)
    library(reshape2)
    
    df = lapply(file_list, function(file) {
                 dat = read_csv(file)
                 dat$source.file = file
                 return(dat) 
          })
    df = bind_rows(df)
    df = dcast(df, id + name ~ source.file, value.var="value")
    

    在上面的代码中,在读入每个文件后,我们添加了一个包含文件名(或其修改版本)的新列source.file。* 然后我们使用dcast 将数据帧从“long”转换为“宽”格式以从每个文件中为value 创建一个单独的列,每个新列采用我们刚刚在source.file 中创建的名称之一。

    另请注意,根据您打算如何处理此数据框,您可能会发现将其保留为长格式(即跳过dcast 步骤)以进行进一步分析更方便。

    附录:处理Aggregation function missing: defaulting to length 警告。当您有不止一行具有相同的idnamesource.file 时,就会发生这种情况。这意味着有多个values 必须映射到同一个单元格,从而导致聚合。默认聚合函数是length(即该单元格中值的计数)。我所知道的唯一解决方法是 (a) 以长格式保存数据,(b) 使用不同的聚合函数(例如,mean),或者 (c) 添加额外的 counter 列来区分大小写idnamesource.file 的相同组合具有多个值。我们在下面演示这些。

    首先,让我们创建一些假数据:

    df = data.frame(id=rep(1:2,2), 
                    name=rep(c("A","B"), 2), 
                    source.file=rep(c("001","002"), each=2),
                    value=11:14)
    
    df
    
      id name source.file value
    1  1    A         001    11
    2  2    B         001    12
    3  1    A         002    13
    4  2    B         002    14
    
    1. idnamesource.file 的每个组合只有一个值,因此 dcast 可以正常工作。

      dcast(df, id + name ~ source.file, value.var="value")
      
        id name 001 002
      1  1    A  11  13
      2  2    B  12  14
      
    2. 添加具有相同idnamesource.file 的附加行。由于现在有两个values 被映射到一个单元格,dcast 必须聚合。默认的聚合函数是提供值的计数。

      df = rbind(df, data.frame(id=1, name="A", source.file="002", value=50))
      
      dcast(df, id + name ~ source.file, value.var="value")
      
      Aggregation function missing: defaulting to length
      
        id name 001 002
      1  1    A   1   2
      2  2    B   1   1
      
    3. 改为使用mean 作为聚合函数。

      dcast(df, id + name ~ source.file, value.var="value", fun.aggregate=mean)
      
        id name 001  002
      1  1    A  11 31.5
      2  2    B  12 14.0
      
    4. 添加一个新的counter 列以区分多行具有相同idnamesource.file 的情况,并将其包含在dcast 中。这使我们回到每个单元格的单个值,但代价是某些source.files 有多个列。

      # Add counter column
      df = df %>% group_by(id, name, source.file) %>%
        mutate(counter=1:n()) 
      

      如您所见,counter 的值仅在 idnamesource.file 的一种组合的情况下具有值 1,但在一种情况下具有值 1 和 2其中有两行具有相同的 idnamesource.file(下面的第 3 行和第 5 行)。

      df
      
           id   name source.file value counter
      1     1      A         001    11       1
      2     2      B         001    12       1
      3     1      A         002    13       1
      4     2      B         002    14       1
      5     1      A         002    50       2
      

      现在我们将dcast 包含在counter 中,因此我们得到两列source.file“002”。

        dcast(df, id + name ~ source.file + counter, value.var="value") 
      
        id name 001_1 002_1 002_2
      1  1    A    11    13    50
      2  2    B    12    14    NA
      

    * 我不确定你的文件名是什么样的,所以你可能需要调整它,创建一个具有唯一文件标识符的命名格式。例如,如果您的文件名遵循“file001.csv”、“file002.csv”等模式,您可以这样做:dat$source.file = paste0("Value", gsub("file([0-9]{3})\\.csv", "\\1", file)

    【讨论】:

    • 嗯,我认为我解释得不够好。我的最终目标是一个数据框,其中包含“id”、“name”列,然后是 150 个文件中每个“值”的 150 列。如果一个文件具有我想要的那个“id”“name”组合的值,如果它没有一个 NA 就可以了。如果我堆叠它们,然后删除重复的“id”“name”条目,我不会删除关联的数据吗?
    • 查看更新后的代码,如果更接近您的要求,请告诉我。
    • 这很接近。我收到一些警告:'> df = bind_rows(df)' 警告消息:1:在 rbind_all(x, .id) 中:不相等的因子级别:强制转换为字符 2:在 rbind_all(x,.id) 中:不相等的因子级别:强制转换为字符 '> df = dcast(df, id + name ~ source.file, value.var="value")' 缺少聚合函数:默认为长度。生成的数据框结构正确,但这些值实际上是该行的每个 .csv 文件中的条目计数,而不是值本身。
    • 因子警告只是意味着原始数据帧中的几列被编码为因子,并在您运行 bind_rows 以将它们放在一个数据帧中时被强制转换为字符。如果您希望这些列再次成为因子,只需将它们转换回因子。否则,您无需担心这些警告。关于第二个问题,请参阅我的答案的附录。
    • 做到了!非常感谢所有的帮助,不要以为我自己会得到这个。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-03-25
    • 1970-01-01
    • 1970-01-01
    • 2019-01-09
    • 2018-11-27
    • 2019-11-22
    • 2018-06-05
    相关资源
    最近更新 更多