我在重新排列以下数据框时遇到问题:
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
我查看了 melt
和 cast
以及其他一些东西,但似乎没有一个可以完成这项工作。
使用 reshape
函数:
reshape(dat1, idvar = "name", timevar = "numbers", direction = "wide")
新的(2014 年)tidyr
软件包也简单地做到了这一点,其中 gather()
/spread()
是 melt
/cast
的术语。
编辑:现在,在 2019 年,tidyr v 1.0 已启动并将 spread
和 gather
设置为弃用路径,而更喜欢 pivot_wider
和 pivot_longer
,您可以找到描述的 {1 }。如果您想简要了解 spread/gather
的短暂生命,请继续阅读。
library(tidyr)
spread(dat1, key = numbers, value = value)
从 github,
tidyr 是 reshape2 的重构,旨在配合 tidy 数据框架,并与 magrittr 和 dplyr 携手合作,为数据分析构建可靠的管道。正如 reshape2 做的比 reshape 少一样,tidyr 做的比 reshape2 少。它是专门为整理数据而设计的,而不是 reshape2 所做的一般重塑,或者 reshape 所做的一般聚合。特别是,内置方法仅适用于数据帧,而 tidyr 不提供边距或聚合。
您可以使用 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")
。
如果性能是一个问题,另一种选择是使用 data.table
对 reshape2
的 melt & 的扩展。广播功能
(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 列的组合时,您会看到差异!
在 tidyr
‘0.8.3.9000’
的开发版本中,有 pivot_wider
和 pivot_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
使用您的示例数据框,我们可以:
xtabs(value ~ name + numbers, data = dat1)
其他两个选项:
基础包:
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)
使用基本 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
基本 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 是包含数值的列
方向指定宽或长格式
可选的 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
是必需的! timevar
和 v.names
部分很简单。这个函数的输出比其他一些函数的输出更可预测,因为一切都是明确定义的。
Win-Vector(制作 vtreat
、seplyr
和 replyr
的人)的天才数据科学家提供了一个非常强大的新软件包,名为 cdata
。它实现了 this document 和本 blog 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
更简单的方法!
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
reshape
带有stats
。更不用说它更快了! =)reshape
是一个糟糕的函数 API 的杰出示例。它非常接近无用。reshape
注释和类似的参数名称并不是很有帮助。但是,我发现从长到宽,您需要提供data =
您的 data.frame,idvar
= 标识您的组的变量,v.names
= 将成为宽格式多列的变量,{ 5} = 包含将以宽格式附加到v.names
、direction = wide
和sep = "_"
的值的变量。够清楚吗? ;)idvars=
,在这种情况下我们可以执行以下操作:reshape(dat1, idvar=c("name1", "name2"), timevar="numbers", direction="wide")