【问题标题】:How to reshape data from long to wide format如何将数据从长格式重塑为宽格式
【发布时间】:2022-01-11 03:44:19
【问题描述】:

我在重新排列以下数据框时遇到问题:

set.seed(45)
dat1 <- data.frame(
    name = rep(c("firstName", "secondName"), each=4),
    numbers = rep(1:4, 2),
    value = rnorm(8)
    )

dat1
       name  numbers      value
1  firstName       1  0.3407997
2  firstName       2 -0.7033403
3  firstName       3 -0.3795377
4  firstName       4 -0.7460474
5 secondName       1 -0.8981073
6 secondName       2 -0.3347941
7 secondName       3 -0.5013782
8 secondName       4 -0.1745357

我想重塑它,使每个唯一的“名称”变量都是一个行名,其中“值”作为沿该行的观察值,而“数字”作为列名。有点像这样:

     name          1          2          3         4
1  firstName  0.3407997 -0.7033403 -0.3795377 -0.7460474
5 secondName -0.8981073 -0.3347941 -0.5013782 -0.1745357

我查看了 meltcast 以及其他一些东西,但似乎没有一个能胜任。

【问题讨论】:

  • @Frank:这是一个更好的标题。 long-formwide-form 是使用的标准术语。通过搜索这些术语无法找到其他答案。
  • 可以在链接的问题中找到更规范的答案,现在名称为Reshape three column data frame to matrix ("long" to "wide" format)。在我看来,最好将这个作为副本关闭。
  • 另一个问题只有一个答案和很多选项这一事实并不一定比这更好;这也有很多选择,但有几个答案。此外,重复的定义是“这个问题已经在这里有答案”(带有指向另一个早先提出的问题的链接)。

标签: r reshape r-faq


【解决方案1】:

您可以使用 reshape() 函数或 reshape 包中的 melt() / cast() 函数来执行此操作。对于第二个选项,示例代码是

library(reshape)
cast(dat1, name ~ numbers)

或使用reshape2

library(reshape2)
dcast(dat1, name ~ numbers)

【讨论】:

  • 值得注意的是,如果您没有明确的“值”列,则仅使用castdcast 将无法正常工作。试试dat &lt;- data.frame(id=c(1,1,2,2),blah=c(8,4,7,6),index=c(1,2,1,2)); dcast(dat, id ~ index); cast(dat, id ~ index),你不会得到你所期望的。例如,您需要明确注意 value/value.var - cast(dat, id ~ index, value="blah")dcast(dat, id ~ index, value.var="blah")
  • 请注意,reshape2 已被弃用,您应该将代码迁移到不再使用它。
  • @dpel 更乐观的说法是,reshape2 终于完成了,您现在可以使用它,而不必担心 Hadley 会再次更改它并破坏您的代码!
【解决方案2】:

使用您的示例数据框,我们可以:

xtabs(value ~ name + numbers, data = dat1)

【讨论】:

  • 这个不错,但结果是格式表,可能不像data.frame或data.table那么容易处理,都有很多包
【解决方案3】:

使用reshape函数:

reshape(dat1, idvar = "name", timevar = "numbers", direction = "wide")

【讨论】:

  • +1 并且你不需要依赖外部包,因为reshape 带有stats。更不用说它更快了! =)
  • @indra_patil - 我可能会使用 reshape2 包,如其他答案之一所示。您可以创建一个特定于您的用例的新问题,如果您无法弄清楚,请发布它。
  • reshape 是一个糟糕的函数 API 的杰出例子。它非常接近无用。
  • reshape cmets 和类似的参数名称并不是那么有用。但是,我发现从长到宽,您需要提供 data = 您的 data.frame,idvar = 标识您的组的变量,v.names = 将成为​​宽格式多列的变量,@ 987654330@ = 包含将以宽格式附加到 v.namesdirection = widesep = "_" 的值的变量。够清楚吗? ;)
  • 我会说基础 R 仍然以大约 2 比 1 的比例赢得投票
【解决方案4】:

新的(2014 年)tidyr 包也很简单,gather()/spread()melt/cast 的术语。

编辑:现在,在 2019 年,tidyr v 1.0 已经发布并将 spreadgather 设置为弃用路径,而不是 pivot_widerpivot_longer,您可以找到描述in this answer。如果您想简要了解spread/gather 的短暂生活,请继续阅读。

library(tidyr)
spread(dat1, key = numbers, value = value)

来自github

tidyrreshape2 的重构,旨在配合整洁的数据框架,并与magrittrdplyr 携手合作,构建可靠的数据分析管道。

正如reshape2 所做的小于重塑一样,tidyr 所做的小于reshape2。它是专门为整理数据而设计的,而不是像reshape2 所做的一般重塑,或者重塑所做的一般聚合。特别是,内置方法仅适用于数据帧,tidyr 不提供边距或聚合。

【讨论】:

  • 只是想添加一个指向R Cookbook 页面的链接,该页面讨论了tidyrreshape2 中这些函数的使用。它提供了很好的例子和解释。
【解决方案5】:

其他两个选项:

基础包:

df <- unstack(dat1, form = value ~ numbers)
rownames(df) <- unique(dat1$name)
df

sqldf 包:

library(sqldf)
sqldf('SELECT name,
      MAX(CASE WHEN numbers = 1 THEN value ELSE NULL END) x1, 
      MAX(CASE WHEN numbers = 2 THEN value ELSE NULL END) x2,
      MAX(CASE WHEN numbers = 3 THEN value ELSE NULL END) x3,
      MAX(CASE WHEN numbers = 4 THEN value ELSE NULL END) x4
      FROM dat1
      GROUP BY name')

【讨论】:

  • 可以这样设置查询,而不是硬编码数字:ValCol &lt;- unique(dat1$numbers);s &lt;- sprintf("MAX(CASE WHEN numbers = %s THEN value ELSE NULL END) `%s`,", ValCol, ValCol);mquerym &lt;- gsub('.{1}$','',paste(s, collapse = "\n"));mquery &lt;- paste("SELECT name,", mquerym, "FROM dat1", "GROUP BY name", sep = "\n");sqldf(mquery)
【解决方案6】:

如果性能受到关注,另一种选择是使用 data.table 扩展 reshape2 的 melt 和 dcast 函数

(Reference: Efficient reshaping using data.tables)

library(data.table)

setDT(dat1)
dcast(dat1, name ~ numbers, value.var = "value")

#          name          1          2         3         4
# 1:  firstName  0.1836433 -0.8356286 1.5952808 0.3295078
# 2: secondName -0.8204684  0.4874291 0.7383247 0.5757814

而且,从 data.table v1.9.6 开始,我们可以对多个列进行强制转换

## add an extra column
dat1[, value2 := value * 2]

## cast multiple value columns
dcast(dat1, name ~ numbers, value.var = c("value", "value2"))

#          name    value_1    value_2   value_3   value_4   value2_1   value2_2 value2_3  value2_4
# 1:  firstName  0.1836433 -0.8356286 1.5952808 0.3295078  0.3672866 -1.6712572 3.190562 0.6590155
# 2: secondName -0.8204684  0.4874291 0.7383247 0.5757814 -1.6409368  0.9748581 1.476649 1.1515627

【讨论】:

  • data.table 方法是最好的!非常有效...当name 是 30-40 列的组合时,您会看到差异!!
  • 如果我想拿最大值怎么办?
  • @T.Fung 我不明白你在问什么。最好打开一个新问题?
  • @SymbolixAU 在 op 的问题 'name' 和 'numbers' 中是唯一的组合。如果它们不是并且我想在旋转后获取每个组合的最大值怎么办?如果问题太繁琐,也不是问题。只是思想的食物。谢谢。
  • 很好的答案。谢谢你。对于多个列,我得到“.subset2(x, i, exact = exact) 中的错误”,并且可以通过强制使用 data.table dcast 来解决此问题:请参阅 stackoverflow.com/a/44271092/190791
【解决方案7】:

使用基础 R aggregate 函数:

aggregate(value ~ name, dat1, I)

# name           value.1  value.2  value.3  value.4
#1 firstName      0.4145  -0.4747   0.0659   -0.5024
#2 secondName    -0.8259   0.1669  -0.8962    0.1681

【讨论】:

    【解决方案8】:

    Win-Vector 的天才数据科学家(制作了 vtreatseplyrreplyr 的人)推出了一个非常强大的新软件包,名为 cdata。它实现了this documentblog post 中描述的“协调数据”原则。这个想法是,无论您如何组织数据,都应该可以使用“数据坐标”系统识别单个数据点。以下是 John Mount 最近博客文章的摘录:

    整个系统基于两个原语或运算符 cdata::moveValuesToRowsD() 和 cdata::moveValuesToColumnsD()。这些 运算符具有枢轴、非枢轴、单热编码、转置、移动 多行和多列,以及许多其他简单特殊的转换 案例。

    很容易编写许多不同的操作 cdata 原语。这些运算符可以在内存或大数据中工作 规模(使用数据库和 Apache Spark;对于大数据,使用 cdata::moveValuesToRowsN() 和 cdata::moveValuesToColumnsN() 变体)。转换由控制表控制,该控制表 本身就是变换的图表(或图片)。

    我们将首先构建控制表(详见blog post),然后执行数据从行到列的移动。

    library(cdata)
    # first build the control table
    pivotControlTable <- buildPivotControlTableD(table = dat1, # reference to dataset
                            columnToTakeKeysFrom = 'numbers', # this will become column headers
                            columnToTakeValuesFrom = 'value', # this contains data
                            sep="_")                          # optional for making column names
    
    # perform the move of data to columns
    dat_wide <- moveValuesToColumnsD(tallTable =  dat1, # reference to dataset
                        keyColumns = c('name'),         # this(these) column(s) should stay untouched 
                        controlTable = pivotControlTable# control table above
                        ) 
    dat_wide
    
    #>         name  numbers_1  numbers_2  numbers_3  numbers_4
    #> 1  firstName  0.3407997 -0.7033403 -0.3795377 -0.7460474
    #> 2 secondName -0.8981073 -0.3347941 -0.5013782 -0.1745357
    

    【讨论】:

      【解决方案9】:

      基本的reshape 函数运行良好:

      df <- data.frame(
        year   = c(rep(2000, 12), rep(2001, 12)),
        month  = rep(1:12, 2),
        values = rnorm(24)
      )
      df_wide <- reshape(df, idvar="year", timevar="month", v.names="values", direction="wide", sep="_")
      df_wide
      

      在哪里

      • idvar 是分隔行的类列
      • timevar 是要广泛转换的类的列
      • v.names 是包含数值的列
      • direction 指定宽格式或长格式
      • 可选的sep 参数是在timevar 类名和输出data.framev.names 之间使用的分隔符。

      如果不存在idvar,则在使用reshape()函数之前创建一个:

      df$id   <- c(rep("year1", 12), rep("year2", 12))
      df_wide <- reshape(df, idvar="id", timevar="month", v.names="values", direction="wide", sep="_")
      df_wide
      

      请记住idvar 是必需的! timevarv.names 部分很简单。这个函数的输出比其他一些函数的输出更容易预测,因为一切都是明确定义的。

      【讨论】:

        【解决方案10】:

        tidyr‘0.8.3.9000’ 的开发版本中,有pivot_widerpivot_longer,它们被概括为从1 列到多列的整形(分别为长-> 宽、宽-> 长)。使用 OP 的数据

        -单列长->宽

        library(dplyr)
        library(tidyr)
        dat1 %>% 
            pivot_wider(names_from = numbers, values_from = value)
        # A tibble: 2 x 5
        #  name          `1`    `2`    `3`    `4`
        #  <fct>       <dbl>  <dbl>  <dbl>  <dbl>
        #1 firstName   0.341 -0.703 -0.380 -0.746
        #2 secondName -0.898 -0.335 -0.501 -0.175
        

        -> 为显示功能创建了另一列

        dat1 %>% 
            mutate(value2 = value * 2) %>% 
            pivot_wider(names_from = numbers, values_from = c("value", "value2"))
        # A tibble: 2 x 9
        #  name       value_1 value_2 value_3 value_4 value2_1 value2_2 value2_3 value2_4
        #  <fct>        <dbl>   <dbl>   <dbl>   <dbl>    <dbl>    <dbl>    <dbl>    <dbl>
        #1 firstName    0.341  -0.703  -0.380  -0.746    0.682   -1.41    -0.759   -1.49 
        #2 secondName  -0.898  -0.335  -0.501  -0.175   -1.80    -0.670   -1.00    -0.349
        

        【讨论】:

          【解决方案11】:

          更简单的方法!

          devtools::install_github("yikeshu0611/onetree") #install onetree package
          
          library(onetree)
          widedata=reshape_toWide(data = dat1,id = "name",j = "numbers",value.var.prefix = "value")
          widedata
          
                  name     value1     value2     value3     value4
             firstName  0.3407997 -0.7033403 -0.3795377 -0.7460474
            secondName -0.8981073 -0.3347941 -0.5013782 -0.1745357
          

          如果你想从宽变长,只需将宽变长,物体没有变化。

          reshape_toLong(data = widedata,id = "name",j = "numbers",value.var.prefix = "value")
          
                  name numbers      value
             firstName       1  0.3407997
            secondName       1 -0.8981073
             firstName       2 -0.7033403
            secondName       2 -0.3347941
             firstName       3 -0.3795377
            secondName       3 -0.5013782
             firstName       4 -0.7460474
            secondName       4 -0.1745357
          

          【讨论】:

            【解决方案12】:

            仅使用 dplyrmap

            library(dplyr)
            library(purrr)
            set.seed(45)
            dat1 <- data.frame(
              name = rep(c("firstName", "secondName"), each=4),
              numbers = rep(1:4, 2), value = rnorm(8)
            )
            longer_to_wider <- function(data, name_from, value_from){
              group <- colnames(data)[!(colnames(data) %in% c(name_from,value_from))]
              data %>% group_by(.data[[group]]) %>%
                summarise( name = list(.data[[name_from]]), 
                           value = list(.data[[value_from]])) %>%
                {
                  d <- data.frame(
                    name = .[[name_from]] %>% unlist() %>% unique()
                  )
                  e <- map_dfc(.[[group]],function(x){
                      y <- data_frame(
                        x = data %>% filter(.data[[group]] == x) %>% pull(value_from)
                      )
                      colnames(y) <- x
                      y
                  })
                  cbind(d,e)
                }
            }
            longer_to_wider(dat1, "name", "value")
            #    name          1          2          3          4
            # 1  firstName  0.3407997 -0.7033403 -0.3795377 -0.7460474
            # 2 secondName -0.8981073 -0.3347941 -0.5013782 -0.1745357
            

            【讨论】:

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