【问题标题】:Reshaping data.frame from wide to long format将 data.frame 从宽格式重塑为长格式
【发布时间】:2021-10-26 13:47:21
【问题描述】:

将我的data.frame 从宽表转换为长表时遇到了一些麻烦。 目前它看起来像这样:

Code Country        1950    1951    1952    1953    1954
AFG  Afghanistan    20,249  21,352  22,532  23,557  24,555
ALB  Albania        8,097   8,986   10,058  11,123  12,246

现在我想把这个data.frame 转换成一个长的data.frame。 像这样的:

Code Country        Year    Value
AFG  Afghanistan    1950    20,249
AFG  Afghanistan    1951    21,352
AFG  Afghanistan    1952    22,532
AFG  Afghanistan    1953    23,557
AFG  Afghanistan    1954    24,555
ALB  Albania        1950    8,097
ALB  Albania        1951    8,986
ALB  Albania        1952    10,058
ALB  Albania        1953    11,123
ALB  Albania        1954    12,246

我已经查看并尝试过使用 melt()reshape() 函数 正如一些人在类似问题中所建议的那样。 但是,到目前为止,我只得到了混乱的结果。

如果可能的话,我想使用 reshape() 函数,因为 它看起来更好处理一些。

【问题讨论】:

  • 不知道是不是这个问题,但是 reshape 包中的函数是熔化和铸造(和重铸)。
  • 并且reshape包已经被reshape2取代了。
  • 现在 reshape2 已经被 tidyr 取代了。
  • 现在tidyrgatherspread 已被pivot_* 函数取代。

标签: r dataframe reshape r-faq


【解决方案1】:

reshape() 需要一段时间才能适应,就像melt/cast。这是一个重塑的解决方案,假设您的数据框称为d

reshape(d, 
        direction = "long",
        varying = list(names(d)[3:7]),
        v.names = "Value",
        idvar = c("Code", "Country"),
        timevar = "Year",
        times = 1950:1954)

【讨论】:

    【解决方案2】:

    使用重塑包:

    #data
    x <- read.table(textConnection(
    "Code Country        1950    1951    1952    1953    1954
    AFG  Afghanistan    20,249  21,352  22,532  23,557  24,555
    ALB  Albania        8,097   8,986   10,058  11,123  12,246"), header=TRUE)
    
    library(reshape)
    
    x2 <- melt(x, id = c("Code", "Country"), variable_name = "Year")
    x2[,"Year"] <- as.numeric(gsub("X", "" , x2[,"Year"]))
    

    【讨论】:

      【解决方案3】:

      三种替代解决方案:

      1) :

      您可以使用与reshape2 包中相同的melt 函数(这是一个扩展和改进的实现)。来自data.tablemelt 也具有比来自reshape2melt 函数更多的参数。例如,您还可以指定变量列的名称:

      library(data.table)
      long <- melt(setDT(wide), id.vars = c("Code","Country"), variable.name = "year")
      

      给出:

      > long
          Code     Country year  value
       1:  AFG Afghanistan 1950 20,249
       2:  ALB     Albania 1950  8,097
       3:  AFG Afghanistan 1951 21,352
       4:  ALB     Albania 1951  8,986
       5:  AFG Afghanistan 1952 22,532
       6:  ALB     Albania 1952 10,058
       7:  AFG Afghanistan 1953 23,557
       8:  ALB     Albania 1953 11,123
       9:  AFG Afghanistan 1954 24,555
      10:  ALB     Albania 1954 12,246
      

      一些替代符号:

      melt(setDT(wide), id.vars = 1:2, variable.name = "year")
      melt(setDT(wide), measure.vars = 3:7, variable.name = "year")
      melt(setDT(wide), measure.vars = as.character(1950:1954), variable.name = "year")
      

      2) 与

      library(tidyr)
      long <- wide %>% gather(year, value, -c(Code, Country))
      

      一些替代符号:

      wide %>% gather(year, value, -Code, -Country)
      wide %>% gather(year, value, -1:-2)
      wide %>% gather(year, value, -(1:2))
      wide %>% gather(year, value, -1, -2)
      wide %>% gather(year, value, 3:7)
      wide %>% gather(year, value, `1950`:`1954`)
      

      3) 与

      library(reshape2)
      long <- melt(wide, id.vars = c("Code", "Country"))
      

      给出相同结果的一些替代符号:

      # you can also define the id-variables by column number
      melt(wide, id.vars = 1:2)
      
      # as an alternative you can also specify the measure-variables
      # all other variables will then be used as id-variables
      melt(wide, measure.vars = 3:7)
      melt(wide, measure.vars = as.character(1950:1954))
      

      注意事项:

      • 已退休。只有将其保留在 CRAN 上所需的更改才会进行。 (source)
      • 如果要排除 NA 值,可以将 na.rm = TRUE 添加到 melt 以及 gather 函数中。

      数据的另一个问题是这些值将被 R 作为字符值读取(由于数字中的 ,)。您可以使用 gsubas.numeric 修复它:

      long$value <- as.numeric(gsub(",", "", long$value))
      

      或者直接用data.table或者dplyr

      # data.table
      long <- melt(setDT(wide),
                   id.vars = c("Code","Country"),
                   variable.name = "year")[, value := as.numeric(gsub(",", "", value))]
      
      # tidyr and dplyr
      long <- wide %>% gather(year, value, -c(Code,Country)) %>% 
        mutate(value = as.numeric(gsub(",", "", value)))
      

      数据:

      wide <- read.table(text="Code Country        1950    1951    1952    1953    1954
      AFG  Afghanistan    20,249  21,352  22,532  23,557  24,555
      ALB  Albania        8,097   8,986   10,058  11,123  12,246", header=TRUE, check.names=FALSE)
      

      【讨论】:

      • 很好的答案,只是一个小小的提醒:不要在你的数据框中放置除idtime 之外的任何变量,melt 无法告诉你在这种情况下想要做什么.
      • @JasonGoal 你能详细说明一下吗?正如我正在解释你的评论,这不应该是一个问题。只需指定id.varsmeasure.vars
      • 抱歉删除这篇文章 - 有人可以向我解释 为什么 3 有效吗?我已经对其进行了测试并且可以正常工作,但是当 dplyr 看到 -c(var1, var2)... 时,我不明白它在做什么
      • @ReputableMisnomer 当tidyr 看到-c(var1, var2) 时,它会在将数据从宽格式转换为长格式时忽略这些变量。
      • 根据tidyverse blog gather 现已退役并由pivot_longer 取代。他们声明:“新的pivot_longer()pivot_wider() 提供了spread()gather() 的现代替代品。它们经过精心重新设计,更易于学习和记忆,并包含许多新功能。spread() 和gather()不会消失,但它们已经退役,这意味着它们不再处于积极开发中。”
      【解决方案4】:

      这是另一个示例,显示了 tidyr 中的 gather 的用法。您可以选择gather 的列,方法是单独删除它们(就像我在这里所做的那样),或者明确包含您想要的年份。

      请注意,为了处理逗号(如果未设置check.names = FALSE,则添加X),我还使用dplyrreadr 中的parse_number 的变异将文本值转换回数字。这些都是tidyverse 的一部分,因此可以与library(tidyverse) 一起加载

      wide %>%
        gather(Year, Value, -Code, -Country) %>%
        mutate(Year = parse_number(Year)
               , Value = parse_number(Value))
      

      返回:

         Code     Country Year Value
      1   AFG Afghanistan 1950 20249
      2   ALB     Albania 1950  8097
      3   AFG Afghanistan 1951 21352
      4   ALB     Albania 1951  8986
      5   AFG Afghanistan 1952 22532
      6   ALB     Albania 1952 10058
      7   AFG Afghanistan 1953 23557
      8   ALB     Albania 1953 11123
      9   AFG Afghanistan 1954 24555
      10  ALB     Albania 1954 12246
      

      【讨论】:

        【解决方案5】:

        由于这个答案被标记为,我觉得分享基本 R 的另一个替代方案会很有用:stack

        但是请注意,stack 不适用于 factors——它仅在 is.vectorTRUE 时才有效,并且从 is.vector 的文档中,我们发现:

        is.vector 返回TRUE,如果 x 是指定模式的向量,没有属性除了名称。否则返回FALSE

        我使用的是样本数据from @Jaap's answer,其中年份列中的值为factors。

        这是stack 方法:

        cbind(wide[1:2], stack(lapply(wide[-c(1, 2)], as.character)))
        ##    Code     Country values  ind
        ## 1   AFG Afghanistan 20,249 1950
        ## 2   ALB     Albania  8,097 1950
        ## 3   AFG Afghanistan 21,352 1951
        ## 4   ALB     Albania  8,986 1951
        ## 5   AFG Afghanistan 22,532 1952
        ## 6   ALB     Albania 10,058 1952
        ## 7   AFG Afghanistan 23,557 1953
        ## 8   ALB     Albania 11,123 1953
        ## 9   AFG Afghanistan 24,555 1954
        ## 10  ALB     Albania 12,246 1954
        

        【讨论】:

          【解决方案6】:

          这是 解决方案:

          sqldf("Select Code, Country, '1950' As Year, `1950` As Value From wide
                  Union All
                 Select Code, Country, '1951' As Year, `1951` As Value From wide
                  Union All
                 Select Code, Country, '1952' As Year, `1952` As Value From wide
                  Union All
                 Select Code, Country, '1953' As Year, `1953` As Value From wide
                  Union All
                 Select Code, Country, '1954' As Year, `1954` As Value From wide;")
          

          要在不输入任何内容的情况下进行查询,您可以使用以下命令:

          感谢 G. Grothendieck 实现它。

          ValCol <- tail(names(wide), -2)
          
          s <- sprintf("Select Code, Country, '%s' As Year, `%s` As Value from wide", ValCol, ValCol)
          mquery <- paste(s, collapse = "\n Union All\n")
          
          cat(mquery) #just to show the query
           #> Select Code, Country, '1950' As Year, `1950` As Value from wide
           #>  Union All
           #> Select Code, Country, '1951' As Year, `1951` As Value from wide
           #>  Union All
           #> Select Code, Country, '1952' As Year, `1952` As Value from wide
           #>  Union All
           #> Select Code, Country, '1953' As Year, `1953` As Value from wide
           #>  Union All
           #> Select Code, Country, '1954' As Year, `1954` As Value from wide
          
          sqldf(mquery)
          
           #>    Code     Country Year  Value
           #> 1   AFG Afghanistan 1950 20,249
           #> 2   ALB     Albania 1950  8,097
           #> 3   AFG Afghanistan 1951 21,352
           #> 4   ALB     Albania 1951  8,986
           #> 5   AFG Afghanistan 1952 22,532
           #> 6   ALB     Albania 1952 10,058
           #> 7   AFG Afghanistan 1953 23,557
           #> 8   ALB     Albania 1953 11,123
           #> 9   AFG Afghanistan 1954 24,555
           #> 10  ALB     Albania 1954 12,246
          

          不幸的是,我认为PIVOTUNPIVOT 不适用于R SQLite。如果您想以更复杂的方式编写查询,还可以查看以下帖子:

          【讨论】:

            【解决方案7】:

            使用tidyr_1.0.0,另一个选项是pivot_longer

            library(tidyr)
            pivot_longer(df1, -c(Code, Country), values_to = "Value", names_to = "Year")
            # A tibble: 10 x 4
            #   Code  Country     Year  Value 
            #   <fct> <fct>       <chr> <fct> 
            # 1 AFG   Afghanistan 1950  20,249
            # 2 AFG   Afghanistan 1951  21,352
            # 3 AFG   Afghanistan 1952  22,532
            # 4 AFG   Afghanistan 1953  23,557
            # 5 AFG   Afghanistan 1954  24,555
            # 6 ALB   Albania     1950  8,097 
            # 7 ALB   Albania     1951  8,986 
            # 8 ALB   Albania     1952  10,058
            # 9 ALB   Albania     1953  11,123
            #10 ALB   Albania     1954  12,246
            

            数据

            df1 <- structure(list(Code = structure(1:2, .Label = c("AFG", "ALB"), class = "factor"), 
                Country = structure(1:2, .Label = c("Afghanistan", "Albania"
                ), class = "factor"), `1950` = structure(1:2, .Label = c("20,249", 
                "8,097"), class = "factor"), `1951` = structure(1:2, .Label = c("21,352", 
                "8,986"), class = "factor"), `1952` = structure(2:1, .Label = c("10,058", 
                "22,532"), class = "factor"), `1953` = structure(2:1, .Label = c("11,123", 
                "23,557"), class = "factor"), `1954` = structure(2:1, .Label = c("12,246", 
                "24,555"), class = "factor")), class = "data.frame", row.names = c(NA, 
            -2L))
            

            【讨论】:

            • 这需要更多的支持。根据Tidyverse Blog gather 正在退休,pivot_longer 现在是实现这一目标的正确方法。
            • @EvanRosica 直到他们决定再次更改功能:p
            【解决方案8】:

            你也可以使用cdata包,它使用(转换)控制表的概念:

            # data
            wide <- read.table(text="Code Country        1950    1951    1952    1953    1954
            AFG  Afghanistan    20,249  21,352  22,532  23,557  24,555
            ALB  Albania        8,097   8,986   10,058  11,123  12,246", header=TRUE, check.names=FALSE)
            
            library(cdata)
            # build control table
            drec <- data.frame(
                Year=as.character(1950:1954),
                Value=as.character(1950:1954),
                stringsAsFactors=FALSE
            )
            drec <- cdata::rowrecs_to_blocks_spec(drec, recordKeys=c("Code", "Country"))
            
            # apply control table
            cdata::layout_by(drec, wide)
            

            我目前正在探索该软件包,并发现它很容易获得。它是为更复杂的转换而设计的,包括反向转换。有a tutorial 可用。

            【讨论】:

              猜你喜欢
              相关资源
              最近更新 更多