如何将数据从长格式重塑为宽格式

How to reshape data from long to wide format

提问人:Steve 提问时间:5/5/2011 最后编辑:NelsonGonSteve 更新时间:11/1/2022 访问量:452075

问:

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

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”变量都是一个行名,“values”作为该行的观察值,“numbers”作为列名。有点像这样:

     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

重塑 R-FAQ

评论

3赞 Frank 10/9/2013
三列数据帧调整为矩阵的可能副本
8赞 smci 4/11/2014
@Frank:这是一个更好的标题。格式和宽格式是使用的标准术语。通过搜索这些术语无法找到另一个答案。
0赞 Aaron left Stack Overflow 10/15/2021
在链接的问题中可以找到更规范的答案,现在的名称为将三列数据帧重塑为矩阵(“长”到“宽”格式)。在我看来,最好将这个作为副本关闭。
0赞 Jaap 10/15/2021
事实上,另一个问题有一个答案,有很多选项,这并不一定比这更好;这也有很多选择,但有几个答案。此外,重复的定义是“这个问题在这里已经有答案了”(带有指向另一个先前提出的问题的链接)。
0赞 Aaron left Stack Overflow 8/1/2023
我每隔一段时间就会检查一次,看看 Stack Overflow 是否仍然比有用更烦人——是的,确实如此。我会不走的。

答:

84赞 Ista 5/5/2011 #1

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

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

或者使用reshape2

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

评论

3赞 thelatemail 6/22/2017
值得注意的是,如果您没有明确的“值”列,则仅使用 OR 将无法很好地工作。尝试,你不会得到你所期望的。您需要明确指出 - 和 例如。castdcastdat <- 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.varcast(dat, id ~ index, value="blah")dcast(dat, id ~ index, value.var="blah")
0赞 dpel 1/21/2021
请注意,reshape2 已弃用,您应该迁移代码,不要使用它。
6赞 Ista 1/23/2021
@dpel 更乐观的说法是说 reshape2 终于完成了,您现在可以使用它了,而不必担心 Hadley 会再次更改它并破坏您的代码!
30赞 user666993 5/5/2011 #2

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

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

评论

3赞 cloudscomputes 10/20/2017
这个很好,但结果是格式化表,它可能不像 data.frame 或 data.table 那么容易处理,两者都有很多包
0赞 nisetama 8/10/2022
结果只是一个带有花哨类名的矩阵。当 的结果为 时,返回 。这使它看起来像一个常规矩阵:.xxtabsattr(x,"class")=NULL;class(x)[1] "matrix" "array"attr(x,"class")=NULL;attr(x,"call")=NULL;dimnames(x)=unname(dimnames(x))
0赞 nisetama 8/10/2022
这会将 的结果转换为 DataFrame: 。如果没有 ,结果将转换回长格式。xtabsclass(x)=NULL;as.data.frame(x)class(x)=NULL
361赞 Chase 5/5/2011 #3

使用功能:reshape

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

评论

24赞 aL3xa 5/5/2011
+1,您不需要依赖外部软件包,因为 .更不用说它更快了!=)reshapestats
10赞 NoBackingDown 10/26/2017
reshape是一个可怕的函数 API 的杰出例子。它非常接近无用。
28赞 Brian D 11/18/2017
注释和类似的参数名称并不是那么有用。但是,我发现,对于从长到宽,您需要提供 data.frame,= 标识组的变量,= 将变成宽格式多列的变量,= 包含将以宽格式附加的值的变量,以及 。够清楚吗?;)reshapedata =idvarv.namestimevarv.namesdirection = widesep = "_"
5赞 vonjd 11/22/2018
我想说的是,基础 R 仍然以大约 2 比 1 的倍数赢得选票
2赞 jay.sf 7/13/2021
有时有两个,在这种情况下,我们可以执行以下操作:idvars=reshape(dat1, idvar=c("name1", "name2"), timevar="numbers", direction="wide")
165赞 Gregor Thomas 7/30/2014 #4

新的(2014 年)包也简单地做到了这一点,其中 / 是 / 的术语。tidyrgather()spread()meltcast

编辑:现在,在 2019 年,tidyr v 1.0 已经启动并设置并走上了弃用路径,而是更喜欢 和 ,您可以在此答案中找到描述。如果您想简要了解 .spreadgatherpivot_widerpivot_longerspread/gather

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

来自 github

tidyr是旨在配合整洁数据框架的重构,并与数据分析携手并构建坚实的管道。reshape2magrittrdplyr

就像 did 小于重塑一样,小于 .它是专门为整理数据而设计的,而不是一般的重塑,或者重塑的一般聚合。具体而言,内置方法仅适用于数据框,不提供边距或聚合。reshape2tidyrreshape2reshape2tidyr

评论

7赞 Jake 4/12/2017
只是想添加一个指向 R Cookbook 页面的链接,该页面讨论 和 中使用这些函数。它提供了很好的例子和解释。tidyrreshape2
26赞 mpalanco 7/15/2015 #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')

评论

1赞 M-- 4/30/2019
可以按如下方式设置查询,而不是对数字进行硬编码:ValCol <- unique(dat1$numbers);s <- sprintf("MAX(CASE WHEN numbers = %s THEN value ELSE NULL END) `%s`,", ValCol, ValCol);mquerym <- gsub('.{1}$','',paste(s, collapse = "\n"));mquery <- paste("SELECT name,", mquerym, "FROM dat1", "GROUP BY name", sep = "\n");sqldf(mquery)
62赞 SymbolixAU 3/28/2016 #6

如果性能是一个问题,另一个选择是使用 的 melt 和 dcast 函数的扩展data.tablereshape2

(参考:使用 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

评论

8赞 joel.wilson 8/31/2017
data.table方法是最好的!非常高效......当是 30-40 列的组合时,您会看到差异!name
0赞 Timothée HENRY 7/3/2019
很好的答案。谢谢。对于多列,我收到“.subset2(x, i, exact = exact)中的错误”,可以通过强制使用 data.table dcast 来解决此问题:请参阅 stackoverflow.com/a/44271092/190791
19赞 Ronak Shah 9/2/2016 #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
11赞 dmi3kno 12/24/2017 #8

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

整个系统基于两个基元或运算符 cdata::moveValuesToRowsD() 和 cdata::moveValuesToColumnsD()。这些 运算符有 pivot、un-pivot、one-hot encode、transpose、moving 多行和多列,以及许多其他简单特殊的转换 例。

根据 CDATA 原语。这些运算符可以在内存中工作,也可以在大数据中工作 scale(使用数据库和 Apache Spark;对于大数据,请使用 cdata::moveValuesToRowsN() 和 cdata::moveValuesToColumnsN() 变体)。转换由控制表控制,该控制表具有 本身是转换的图(或图片)。

我们将首先构建控制表(有关详细信息,请参阅博客文章),然后执行数据从行到列的移动。

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

评论

0赞 runr 1/6/2022
答案需要更新,因为包似乎被重写了(链接已经死了)
15赞 Adam Erickson 8/3/2018 #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指定宽格式或长格式
  • 可选参数是在类名之间和输出中使用的分隔符。septimevarv.namesdata.frame

如果不存在,请在使用该函数之前创建一个:idvarreshape()

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

请记住,这是必需的!和部分很容易。此函数的输出比其他一些函数更可预测,因为所有内容都是显式定义的。idvartimevarv.names

57赞 akrun 7/13/2019 #10

有了tidyr,就有和分别从长->宽或宽->长做重塑。使用 OP 的数据:pivot_wider()pivot_longer()

单柱长->宽

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

多列长 -> 宽

pivot_wider()还能够进行更复杂的枢轴操作。例如,您可以同时透视多个列:

# create another column for showing the functionality
dat2 <- dat1 %>% 
    dplyr::rename(valA = value) %>%
    dplyr::mutate(valB = valA * 2) 

dat2 %>% 
    pivot_wider(names_from = numbers, values_from = c(valA, valB))

# # A tibble: 2 × 9
#   name       valA_1 valA_2 valA_3 valA_4 valB_1 valB_2 valB_3 valB_4
#   <chr>       <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

在文档中可以找到更多功能。

5赞 zhang jing 7/26/2019 #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
-1赞 fmassica 11/2/2021 #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
0赞 nisetama 8/10/2022 #13

即使您缺少对并且不需要排序(可以替换为):as.matrix(dat1)[,1:2]cbind(dat1[,1],dat1[,2])

> set.seed(45);dat1=data.frame(name=rep(c("firstName","secondName"),each=4),numbers=rep(1:4,2),value=rnorm(8))
> u1=unique(dat1[,1]);u2=unique(dat1[,2])
> m=matrix(nrow=length(u1),ncol=length(u2),dimnames=list(u1,u2))
> m[as.matrix(dat1)[,1:2]]=dat1[,3]
> m
                    1          2          3          4
firstName   0.3407997 -0.7033403 -0.3795377 -0.7460474
secondName -0.8981073 -0.3347941 -0.5013782 -0.1745357

如果您缺少对并且需要排序,则这不起作用,但如果对已经排序,则速度会短一些:

> u1=unique(dat1[,1]);u2=unique(dat1[,2])
> dat1=dat1[order(dat1[,1],dat1[,2]),] # not actually needed in this case
> matrix(dat1[,3],length(u1),,T,list(u1,u2))
                    1          2          3          4
firstName   0.3407997 -0.7033403 -0.3795377 -0.7460474
secondName -0.8981073 -0.3347941 -0.5013782 -0.1745357

这是第一种方法的函数版本(添加以使其与 tibbles 一起使用):as.data.frame

l2w=function(x,row=1,col=2,val=3,sort=F){
  u1=unique(x[,row])
  u2=unique(x[,col])
  if(sort){u1=sort(u1);u2=sort(u2)}
  out=matrix(nrow=length(u1),ncol=length(u2),dimnames=list(u1,u2))
  out[cbind(x[,row],x[,col])]=x[,val]
  out
}

或者,如果您只有下三角形的值,则可以这样做:

> euro=as.matrix(eurodist)[1:3,1:3]
> lower=data.frame(V1=rownames(euro)[row(euro)[lower.tri(euro)]],V2=colnames(euro)[col(euro)[lower.tri(euro)]],V3=euro[lower.tri(euro)])
> lower
         V1        V2   V3
1 Barcelona    Athens 3313
2  Brussels    Athens 2963
3  Brussels Barcelona 1318
> n=unique(c(lower[,1],lower[,2]))
> full=rbind(lower,setNames(lower[,c(2,1,3)],names(lower)),data.frame(V1=n,V2=n,V3=0))
> full
         V1        V2   V3
1 Barcelona    Athens 3313
2  Brussels    Athens 2963
3  Brussels Barcelona 1318
4    Athens Barcelona 3313
5    Athens  Brussels 2963
6 Barcelona  Brussels 1318
7    Athens    Athens    0
8 Barcelona Barcelona    0
9  Brussels  Brussels    0
> l2w(full,sort=T)
          Athens Barcelona Brussels
Athens         0      3313     2963
Barcelona   3313         0     1318
Brussels    2963      1318        0

或者这是另一种方法:

> rc=as.matrix(lower[-3])
> n=sort(unique(c(rc)))
> m=matrix(0,length(n),length(n),,list(n,n))
> m[rc]=lower[,3]
> m[rc[,2:1]]=lower[,3]
> m
          Athens Barcelona Brussels
Athens         0      3313     2963
Barcelona   3313         0     1318
Brussels    2963      1318        0

基础 R 中的另一种简单方法是使用 .的结果基本上只是一个带有花哨类名的矩阵,但你可以用以下命令使它看起来像一个常规矩阵:xtabsxtabsclass(x)=NULL;attr(x,"call")=NULL;dimnames(x)=unname(dimnames(x))

> x=xtabs(value~name+numbers,dat1);x
            numbers
name                  1          2          3          4
  firstName   0.3407997 -0.7033403 -0.3795377 -0.7460474
  secondName -0.8981073 -0.3347941 -0.5013782 -0.1745357
> str(x)
 'xtabs' num [1:2, 1:4] 0.341 -0.898 -0.703 -0.335 -0.38 ...
 - attr(*, "dimnames")=List of 2
  ..$ name   : chr [1:2] "firstName" "secondName"
  ..$ numbers: chr [1:4] "1" "2" "3" "4"
 - attr(*, "call")= language xtabs(formula = value ~ name + numbers, data = dat1)
> class(x)
[1] "xtabs" "table"
> class(as.matrix(x)) # `as.matrix` has no effect because `x` is already a matrix
[1] "xtabs" "table"
> class(x)=NULL;class(x)
[1] "matrix" "array"
> attr(x,"call")=NULL;dimnames(x)=unname(dimnames(x))
> x # now it looks like a regular matrix
                    1          2          3          4
firstName   0.3407997 -0.7033403 -0.3795377 -0.7460474
secondName -0.8981073 -0.3347941 -0.5013782 -0.1745357
> str(x)
 num [1:2, 1:4] 0.341 -0.898 -0.703 -0.335 -0.38 ...
 - attr(*, "dimnames")=List of 2
  ..$ : chr [1:2] "firstName" "secondName"
  ..$ : chr [1:4] "1" "2" "3" "4"

通常将结果转换回长格式,但您可以通过以下方式避免它:as.data.frame(x)xtabsclass(x)=NULL

> x=xtabs(value~name+numbers,dat1);as.data.frame(x)
        name numbers       Freq
1  firstName       1  0.3407997
2 secondName       1 -0.8981073
3  firstName       2 -0.7033403
4 secondName       2 -0.3347941
5  firstName       3 -0.3795377
6 secondName       3 -0.5013782
7  firstName       4 -0.7460474
8 secondName       4 -0.1745357
> class(x)=NULL;as.data.frame(x)
                    1          2          3          4
firstName   0.3407997 -0.7033403 -0.3795377 -0.7460474
secondName -0.8981073 -0.3347941 -0.5013782 -0.1745357

这会将宽 from at 格式的数据转换为长格式(将数据帧转换为向量,并将矩阵转换为向量):unlistc

w2l=function(x)data.frame(V1=rownames(x)[row(x)],V2=colnames(x)[col(x)],V3=unname(c(unlist(x))))
0赞 BKS 11/1/2022 #14

通过一个链接的问题来到这里:将三列数据框重塑为矩阵(“长”到“宽”格式)。这个问题已经结束了,所以我在这里写了一个替代解决方案。

我找到了另一种解决方案,也许对希望将三列转换为矩阵的人有用。我指的是解耦R(2.3.2)包。以下是从他们的网站复制的


生成一种表,其中行来自 id_cols,列来自 names_from,值来自 values_from。

用法

pivot_wider_profile(
data,
id_cols,
names_from,
values_from,
values_fill = NA,
to_matrix = FALSE,
to_sparse = FALSE,
...
)