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

我有麻烦重新安排以下数据帧:

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”变量都是一个行名,“值”作为该行的观察值,“数字”作为冒号。就像这样:

     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和其他一些东西,但似乎没有做的工作。

386092 次浏览

你可以使用reshape()函数,或者使用重塑包中的melt() / cast()函数来实现这一点。对于第二个选项,示例代码为

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

或者使用reshape2

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

使用你的例子数据框架,我们可以:

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

使用reshape函数:

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

新的(2014年)tidyr包也简单地做到了这一点,其中gather()/spread()melt/cast的术语。

现在,在2019年,tidyr v 1.0已经推出,并将spreadgather设置为弃用路径,而更倾向于pivot_widerpivot_longer,你可以在在这个答案中中找到描述。如果你想简要了解spread/gather的短暂生命,请继续阅读。

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

github,

tidyrreshape2的一个重构,旨在配合整洁的数据框架,并与magrittrdplyr一起工作,为数据分析构建一个坚实的管道。

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

其他两种选择:

基本包:

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')

如果关心性能,另一个选择是使用data.tablereshape2的melt &dcast功能

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

至于数据。表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

使用base 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

Win-Vector的天才数据科学家(他们制作了vtreatseplyrreplyr)提供了一个非常强大的新包,名为cdata。它实现了这个文档和this 博客中描述的“协调数据”原则。其思想是,无论如何组织数据,都应该能够使用“数据坐标”系统识别单个数据点。下面是约翰·芒特最近博客文章的节选:

整个系统基于两个原语或操作符 cdata::moveValuesToRowsD()和cdata::moveValuesToColumnsD()。这些 运算符有枢轴,反枢轴,单热编码,转置,移动 多行和多列,以及许多其他转换一样简单特殊 用例。< / p > 可以很容易地写出许多不同的操作 cdata原语。这些操作符可以在内存或大数据中工作 规模(与数据库和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

基函数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是输出data.frame中用于timevar类名和v.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部分很简单。这个函数的输出比其他一些函数更可预测,因为所有内容都是显式定义的。

对于tidyr,有pivot_wider()pivot_longer(),它们被广义用于从long ->Wide或Wide ->长,分别。使用OP的数据:

单柱长->宽

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

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

简单多了!

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

只使用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

即使你有缺少的对,它也可以工作,并且不需要排序(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

下面是第一个方法的函数版本(添加as.data.frame使其与tibbles一起工作):

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

base R中的另一个简单方法是使用xtabsxtabs的结果基本上只是一个带有花哨类名的矩阵,但你可以使用class(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)会将xtabs的结果转换回长格式,但你可以使用class(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

这个函数将宽格式的数据转换为长格式(unlist将数据帧转换为向量,c将矩阵转换为向量):

w2l=function(x)data.frame(V1=rownames(x)[row(x)],V2=colnames(x)[col(x)],V3=unname(c(unlist(x))))

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

我找到了另一种解决方案,可能对寻找将三列转换为矩阵的人有用。我指的是去耦(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,
...
)