长与宽,整洁与高效
long vs wide, tidy vs efficient
当涉及到在长格式和宽格式之间切换时,我发现当前数据分析工作流程中有一些次优步骤。考虑下面显示的三个轨迹,具有共同的 x
值,
我的数据是长格式的,对于绘图和各种花哨的东西很有用,但对于分析的某些部分来说,处理宽格式(类似矩阵)似乎要容易得多。例如,在这个虚拟示例中,我可能想将所有迹线的基线设置为 0,方法是减去 0 到 0.25 之间的每条迹线的平均值(阴影灰色区域)。
我找不到一种简单的方法来以长格式执行此类操作。
我目前的策略是切换回宽格式,但 i) 我从来不记得 dcast/reshape 的正确语法,ii) 在两者之间来回切换效率很低。
dwide <- reshape2::dcast(dlong, x~..., value.var="y")
dwide[,-1] <- sweep(dwide[,-1], 2, colMeans(dwide[dwide$x < 0.25, -1]), FUN="-")
dlong2 <- melt(dwide, id="x")
我是否错过了一些有用的工具?我愿意接受 data.table 建议。
完整的可重现示例:
library(ggplot2)
library(plyr)
library(reshape2)
## dummy data as noisy lorentzian-shaped peaks with random offset
set.seed(1234)
fake_data <- function(a, x = seq(0, 1, length=100)){
data.frame(x = x,
y = jitter(1e-3 / ((x - a)^2 + 1e-3) + runif(1,0,1),
amount = 0.1))
}
## apply function to all combinations of parameters (one here)
dlong <- plyr::mdply(data.frame(a = c(0.4,0.5,0.6)), fake_data)
ggplot(dlong, aes(x, y, colour=factor(a))) + geom_line() +
annotate("rect", xmin=-Inf, xmax=0.25, ymin=-Inf, ymax=Inf, fill="grey", alpha = 0.3) +
theme_minimal()
dwide <- reshape2::dcast(dlong, x~..., value.var="y")
str(dwide)
dwide[,-1] <- sweep(dwide[,-1], 2, colMeans(dwide[dwide$x < 0.25, -1]), FUN="-")
dlong2 <- melt(dwide, id="x")
ggplot(dlong2, aes(x, value, colour=variable)) + geom_line() +
theme_minimal()
也许您的最小示例太微不足道,无法涵盖您可能想要从长到宽再到长的所有情况。但至少对于你的例子,我通常会使用 data.table 进行这种操作:
setDT(dlong)[, y2 := y - mean(y[x < 0.25]), by=a]
ggplot(dlong, aes(x, y2, colour=factor(a))) +
geom_line() +
theme_minimal()
分解:
by = a
对数据进行分组,以便 [
.data.table 的第二个参数中的操作应用于对应于 a 的每个值的 dlong 子集
y2 := y - mean(y[x < 0.25])
因此对a的每个值分别计算
:=
是data.table中的一个特殊运算符,它提供引用赋值而不是复制赋值(非常高效)
[
.datat.table的第一个参数在这里留空,因为我们希望对原始dlong数据的所有行进行操作。
可以用 dplyr
完成几乎相同的事情
dlong %>%
group_by(a) %>%
mutate(y2 = y - mean(y[x < 0.25]))
当涉及到在长格式和宽格式之间切换时,我发现当前数据分析工作流程中有一些次优步骤。考虑下面显示的三个轨迹,具有共同的 x
值,
我的数据是长格式的,对于绘图和各种花哨的东西很有用,但对于分析的某些部分来说,处理宽格式(类似矩阵)似乎要容易得多。例如,在这个虚拟示例中,我可能想将所有迹线的基线设置为 0,方法是减去 0 到 0.25 之间的每条迹线的平均值(阴影灰色区域)。
我找不到一种简单的方法来以长格式执行此类操作。
我目前的策略是切换回宽格式,但 i) 我从来不记得 dcast/reshape 的正确语法,ii) 在两者之间来回切换效率很低。
dwide <- reshape2::dcast(dlong, x~..., value.var="y")
dwide[,-1] <- sweep(dwide[,-1], 2, colMeans(dwide[dwide$x < 0.25, -1]), FUN="-")
dlong2 <- melt(dwide, id="x")
我是否错过了一些有用的工具?我愿意接受 data.table 建议。
完整的可重现示例:
library(ggplot2)
library(plyr)
library(reshape2)
## dummy data as noisy lorentzian-shaped peaks with random offset
set.seed(1234)
fake_data <- function(a, x = seq(0, 1, length=100)){
data.frame(x = x,
y = jitter(1e-3 / ((x - a)^2 + 1e-3) + runif(1,0,1),
amount = 0.1))
}
## apply function to all combinations of parameters (one here)
dlong <- plyr::mdply(data.frame(a = c(0.4,0.5,0.6)), fake_data)
ggplot(dlong, aes(x, y, colour=factor(a))) + geom_line() +
annotate("rect", xmin=-Inf, xmax=0.25, ymin=-Inf, ymax=Inf, fill="grey", alpha = 0.3) +
theme_minimal()
dwide <- reshape2::dcast(dlong, x~..., value.var="y")
str(dwide)
dwide[,-1] <- sweep(dwide[,-1], 2, colMeans(dwide[dwide$x < 0.25, -1]), FUN="-")
dlong2 <- melt(dwide, id="x")
ggplot(dlong2, aes(x, value, colour=variable)) + geom_line() +
theme_minimal()
也许您的最小示例太微不足道,无法涵盖您可能想要从长到宽再到长的所有情况。但至少对于你的例子,我通常会使用 data.table 进行这种操作:
setDT(dlong)[, y2 := y - mean(y[x < 0.25]), by=a]
ggplot(dlong, aes(x, y2, colour=factor(a))) +
geom_line() +
theme_minimal()
分解:
by = a
对数据进行分组,以便[
.data.table 的第二个参数中的操作应用于对应于 a 的每个值的 dlong 子集y2 := y - mean(y[x < 0.25])
因此对a的每个值分别计算:=
是data.table中的一个特殊运算符,它提供引用赋值而不是复制赋值(非常高效)[
.datat.table的第一个参数在这里留空,因为我们希望对原始dlong数据的所有行进行操作。
dplyr
完成几乎相同的事情
dlong %>%
group_by(a) %>%
mutate(y2 = y - mean(y[x < 0.25]))