【问题标题】:Transpose / reshape dataframe without "timevar" from long to wide format将没有“timevar”的数据帧从长格式转换为宽格式
【发布时间】:2021-10-08 18:09:10
【问题描述】:

我有一个遵循以下长模式的数据框:

   Name          MedName
  Name1    atenolol 25mg
  Name1     aspirin 81mg
  Name1 sildenafil 100mg
  Name2    atenolol 50mg
  Name2   enalapril 20mg

并且想得到下面(我不在乎我是否可以这样命名列,只想要这种格式的数据):

   Name   medication1    medication2      medication3
  Name1 atenolol 25mg   aspirin 81mg sildenafil 100mg
  Name2 atenolol 50mg enalapril 20mg             NA

通过这个网站,我已经熟悉了 reshape/reshape2 包,并且已经尝试了几次试图让它工作,但到目前为止都失败了。

当我尝试dcast(dataframe, Name ~ MedName, value.var='MedName') 时,我只得到一堆作为药物名称标志的列(转置的值为 1 或 0)示例:

 Name  atenolol 25mg  aspirin 81mg
Name1              1             1
Name2              0             0 

我在融化数据集后也尝试了dcast(dataset, Name ~ variable),但这只是吐出以下内容(只计算每个人有多少药物):

 Name  MedName
Name1        3
name2        2

最后,我尝试融合数据,然后使用 idvar="Name" timevar="variable" (其中所有只是医学名称)重塑数据,但这似乎不是针对我的问题,因为如果有多个匹配的 idvar, reshape 只取第一个 MedName 并忽略其余部分。

有谁知道如何使用 reshape 或其他 R 函数来做到这一点?我意识到可能有一种方法可以通过一些 for 循环和条件来以更混乱的方式执行此操作,以基本上拆分和重新粘贴数据,但我希望有一个更简单的解决方案。非常感谢!

【问题讨论】:

    标签: r reshape transpose r-faq


    【解决方案1】:

    使用 包,可以使用新的rowid 函数轻松解决此问题:

    library(data.table)
    dcast(setDT(d1), 
          Name ~ rowid(Name, prefix = "medication"), 
          value.var = "MedName")
    

    给出:

       Name    medication1     medication2       medication3
    1 Name1  atenolol 25mg    aspirin 81mg  sildenafil 100mg
    2 Name2  atenolol 50mg  enalapril 20mg              <NA>
    

    另一种方法(1.9.7版本之前常用):

    dcast(setDT(d1)[, rn := 1:.N, by = Name], 
          Name ~ paste0("medication",rn), 
          value.var = "MedName")
    

    给出相同的结果。


    类似的方法,但现在使用 包:

    library(dplyr)
    library(tidyr)
    d1 %>%
      group_by(Name) %>%
      mutate(rn = paste0("medication",row_number())) %>%
      spread(rn, MedName)
    

    给出:

    Source: local data frame [2 x 4]
    Groups: Name [2]
    
        Name   medication1    medication2      medication3
      (fctr)         (chr)          (chr)            (chr)
    1  Name1 atenolol 25mg   aspirin 81mg sildenafil 100mg
    2  Name2 atenolol 50mg enalapril 20mg               NA
    

    【讨论】:

      【解决方案2】:

      假设您的数据在对象dataset

      library(plyr)
      ## Add a medication index
      data_with_index <- ddply(dataset, .(Name), mutate, 
                               index = paste0('medication', 1:length(Name)))    
      dcast(data_with_index, Name ~ index, value.var = 'MedName')
      
      ##    Name   medication1    medication2      medication3
      ## 1 Name1 atenolol 25mg   aspirin 81mg sildenafil 100mg
      ## 2 Name2 atenolol 50mg enalapril 20mg             <NA>
      

      【讨论】:

      • 这个解决方案有小问题。当列数超过 10 列时,以 strage 方式排序。例如开始medication1,medication10,medication11,medication12,...,medication2。如何解决排序问题?
      【解决方案3】:

      在使用reshape 之前,您始终可以生成唯一的timevar。这里我使用ave 来应用函数seq_along 'along' 每个“名称”。

      test <- data.frame(
      Name=c(rep("name1",3),rep("name2",2)),
      MedName=c("atenolol 25mg","aspirin 81mg","sildenafil 100mg",
                "atenolol 50mg","enalapril 20mg")
      )
      
      # generate the 'timevar'
      test$uniqid <- with(test, ave(as.character(Name), Name, FUN = seq_along))
      
      # reshape!
      reshape(test, idvar = "Name", timevar = "uniqid", direction = "wide")
      

      结果:

         Name     MedName.1      MedName.2        MedName.3
      1 name1 atenolol 25mg   aspirin 81mg sildenafil 100mg
      4 name2 atenolol 50mg enalapril 20mg             <NA>
      

      【讨论】:

      • 感谢您的帮助,这有效。我对这些列的担心是,在我的实际数据集中,我有一个不断变化的药物数量和名称,因此声明 MedName=c(All the names) 可能有点多,但我感谢您的帮助,并将可能在其他问题上使用此方法。
      • @Hotamd6 - 无需手动指定所有名称 - 您只需对 gsub("MedName.","medication",names(reshapedtestdata),fixed=TRUE) 等数据集名称进行查找和替换即可获得与上述 @mnel 相同的结果。
      【解决方案4】:

      这似乎实际上是一个相当普遍的问题,所以我在“splitstackshape”包中包含了一个名为getanID 的函数。

      它的作用如下:

      library(splitstackshape)
      getanID(test, "Name")
      #     Name          MedName .id
      # 1: name1    atenolol 25mg   1
      # 2: name1     aspirin 81mg   2
      # 3: name1 sildenafil 100mg   3
      # 4: name2    atenolol 50mg   1
      # 5: name2   enalapril 20mg   2
      

      由于“data.table”与“splitstackshape”一起加载,您可以访问dcast.data.table,因此您可以按照@mnel 的示例进行操作。

      dcast.data.table(getanID(test, "Name"), Name ~ .id, value.var = "MedName")
      #     Name             1              2                3
      # 1: name1 atenolol 25mg   aspirin 81mg sildenafil 100mg
      # 2: name2 atenolol 50mg enalapril 20mg               NA
      

      该函数本质上实现了一个sequence(.N),由标识的组创建“时间”列。

      【讨论】:

        【解决方案5】:

        @thelatemail 的解决方案与此类似。当我生成时间变量时,我使用rle,以防我不以交互方式工作并且Name 变量需要是动态的。

        # start with your example data
        x <- 
            data.frame(
                Name=c(rep("name1",3),rep("name2",2)),
                MedName=c("atenolol 25mg","aspirin 81mg","sildenafil 100mg",
                    "atenolol 50mg","enalapril 20mg")
            )
        
        # pick the id variable
        id <- 'Name'
        
        # sort the data.frame by that variable
        x <- x[ order( x[ , id ] ) , ]
        
        # construct a `time` variable on the fly
        x$time <- unlist( lapply( rle( as.character( x[ , id ] ) )$lengths , seq_len ) )
        
        # `reshape` uses that new `time` column by default
        y <- reshape( x , idvar = id , direction = 'wide' )
        
        # done
        y
        

        【讨论】:

        • 我不确定我是否理解您关于在“名称”变量需要动态时使用 rle 的评论。 @thelatemail 的解决方案不是也允许这种灵活性(并且不必先对数据进行排序)吗?
        • @AnandaMahto 也许你是对的..我想你可以在第二行使用id &lt;- 'Name',然后再使用as.character(get(id)),然后其余的都是动态的。
        【解决方案6】:

        一个干净的解决方案涉及来自tidyr 包版本1.1.0 的非常有用的pivot_wider 函数。这样,您还可以使用参数names_glue 直接指定列名。

        library(tidyr)
        library(dplyr)
        
        dataframe %>% 
          group_by(Name) %>% 
          mutate(row_n = row_number()) %>% 
          pivot_wider(id_cols = Name, names_from = row_n, values_from = MedName, names_glue = "medication{row_n}")
        

        输出

        # A tibble: 2 x 4
        # Groups:   Name [2]
        #   Name  medication1   medication2    medication3     
        #   <chr> <chr>         <chr>          <chr>           
        # 1 Name1 atenolol 25mg aspirin 81mg   sildenafil 100mg
        # 2 Name2 atenolol 50mg enalapril 20mg NA  
        

        【讨论】:

          【解决方案7】:

          带有chop()unnest_wider()tidyr 解决方案。

          library(tidyr)
          
          df %>%
            chop(-Name) %>%
            unnest_wider(MedName, names_sep = "")
          
          # # A tibble: 2 x 4
          #   Name  MedName1      MedName2       MedName3        
          #   <chr> <chr>         <chr>          <chr>           
          # 1 Name1 atenolol 25mg aspirin 81mg   sildenafil 100mg
          # 2 Name2 atenolol 50mg enalapril 20mg NA 
          

          参数names_sep = "" 是必要的;否则,新列名称将为..1..2..3


          数据

          df <- structure(list(Name = c("Name1", "Name1", "Name1", "Name2", "Name2"
          ), MedName = c("atenolol 25mg", "aspirin 81mg", "sildenafil 100mg", 
          "atenolol 50mg", "enalapril 20mg")), class = "data.frame", row.names = c(NA, -5L))
          

          【讨论】:

            【解决方案8】:

            这是一个更短的方法,利用unlist 处理名称的方式:

            library(dplyr)
            df1 %>% group_by(Name) %>% do(as_tibble(t(unlist(.[2]))))
            # # A tibble: 2 x 4
            # # Groups:   Name [2]
            #      Name      MedName1       MedName2         MedName3
            #     <chr>         <chr>          <chr>            <chr>
            #   1 name1 atenolol 25mg   aspirin 81mg sildenafil 100mg
            #   2 name2 atenolol 50mg enalapril 20mg             <NA>
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2013-10-22
              相关资源
              最近更新 更多