提问人:Steve 提问时间:5/5/2011 最后编辑:NelsonGonSteve 更新时间:11/1/2022 访问量:452075
如何将数据从长格式重塑为宽格式
How to reshape data from long to wide format
问:
我在重新排列以下数据框时遇到问题:
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
我看过其他一些东西,但似乎没有一个能完成这项工作。melt
cast
答:
您可以使用函数或 reshape 包中的 / 函数执行此操作。对于第二个选项,示例代码是reshape()
melt()
cast()
library(reshape)
cast(dat1, name ~ numbers)
或者使用reshape2
library(reshape2)
dcast(dat1, name ~ numbers)
评论
cast
dcast
dat <- 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")
使用您的示例数据帧,我们可以:
xtabs(value ~ name + numbers, data = dat1)
评论
x
xtabs
attr(x,"class")=NULL;class(x)
[1] "matrix" "array"
attr(x,"class")=NULL;attr(x,"call")=NULL;dimnames(x)=unname(dimnames(x))
xtabs
class(x)=NULL;as.data.frame(x)
class(x)=NULL
使用功能:reshape
reshape(dat1, idvar = "name", timevar = "numbers", direction = "wide")
评论
reshape
stats
reshape
是一个可怕的函数 API 的杰出例子。它非常接近无用。
reshape
data =
idvar
v.names
timevar
v.names
direction = wide
sep = "_"
idvars=
reshape(dat1, idvar=c("name1", "name2"), timevar="numbers", direction="wide")
新的(2014 年)包也简单地做到了这一点,其中 / 是 / 的术语。tidyr
gather()
spread()
melt
cast
编辑:现在,在 2019 年,tidyr v 1.0 已经启动并设置并走上了弃用路径,而是更喜欢 和 ,您可以在此答案中找到描述。如果您想简要了解 .spread
gather
pivot_wider
pivot_longer
spread/gather
library(tidyr)
spread(dat1, key = numbers, value = value)
来自 github,
tidyr
是旨在配合整洁数据框架的重构,并与数据分析携手并构建坚实的管道。reshape2
magrittr
dplyr
就像 did 小于重塑一样,小于 .它是专门为整理数据而设计的,而不是一般的重塑,或者重塑的一般聚合。具体而言,内置方法仅适用于数据框,不提供边距或聚合。
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')
评论
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)
如果性能是一个问题,另一个选择是使用 的 melt 和 dcast 函数的扩展data.table
reshape2
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
方法是最好的!非常高效......当是 30-40 列的组合时,您会看到差异!name
使用基本 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 的天才数据科学家(制作 和 的人)推出了一个非常强大的新软件包,称为 .它实现了本文档和本博客文章中描述的“协调数据”原则。这个想法是,无论您如何组织数据,都应该可以使用“数据坐标”系统来识别单个数据点。以下是约翰·芒特(John Mount)最近发表的博客文章的摘录:vtreat
seplyr
replyr
cdata
整个系统基于两个基元或运算符 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
评论
基本函数工作得很好: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
v.names
data.frame
如果不存在,请在使用该函数之前创建一个: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
timevar
v.names
有了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
在文档中可以找到更多功能。
更简单的方法!
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
仅使用 和 .dplyr
map
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
这是第一种方法的函数版本(添加以使其与 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 中的另一种简单方法是使用 .的结果基本上只是一个带有花哨类名的矩阵,但你可以用以下命令使它看起来像一个常规矩阵:xtabs
xtabs
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
这会将宽 from at 格式的数据转换为长格式(将数据帧转换为向量,并将矩阵转换为向量):unlist
c
w2l=function(x)data.frame(V1=rownames(x)[row(x)],V2=colnames(x)[col(x)],V3=unname(c(unlist(x))))
通过一个链接的问题来到这里:将三列数据框重塑为矩阵(“长”到“宽”格式)。这个问题已经结束了,所以我在这里写了一个替代解决方案。
我找到了另一种解决方案,也许对希望将三列转换为矩阵的人有用。我指的是解耦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,
...
)
评论