如前所述,许多更强大的分析和可视化工具都依赖于长格式数据。当然,对于受益于矩阵代数的转换,您应该将内容保存在数组中,但是一旦您想要对数据子集进行并行分析,或者根据数据中的因素绘制内容,您真的想要melt。
下面是一个示例,可帮助您开始使用 data.table 和 ggplot。
数组 -> 数据表
首先,让我们按照您的格式制作一些数据:
series <- 3
samples <- 2
trials <- 4
trial.labs <- paste("tr", seq(len=trials))
trial.class <- sample(c("A", "B"), trials, rep=T)
arr <- array(
runif(series * samples * trials),
dim=c(series, samples, trials),
dimnames=list(
ser=paste("ser", seq(len=series)),
smp=paste("smp", seq(len=samples)),
tr=trial.labs
)
)
# , , tr = Trial 1
# smp
# ser smp 1 smp 2
# ser 1 0.9648542 0.4134501
# ser 2 0.7285704 0.1393077
# ser 3 0.3142587 0.1012979
#
# ... omitted 2 trials ...
#
# , , tr = Trial 4
# smp
# ser smp 1 smp 2
# ser 1 0.5867905 0.5160964
# ser 2 0.2432201 0.7702306
# ser 3 0.2671743 0.8568685
现在我们有了一个 3 维数组。让我们将melt 转换为data.table(注意melt 在data.frames 上运行,基本上是data.tables 没有花里胡哨,所以我们必须先融化,然后转换为data.table):
library(reshape2)
library(data.table)
dt.raw <- data.table(melt(arr), key="tr") # we'll get to what the `key` arg is doing later
# ser smp tr value
# 1: ser 1 smp 1 tr 1 0.53178276
# 2: ser 2 smp 1 tr 1 0.28574271
# 3: ser 3 smp 1 tr 1 0.62991366
# 4: ser 1 smp 2 tr 1 0.31073376
# 5: ser 2 smp 2 tr 1 0.36098971
# ---
# 20: ser 2 smp 1 tr 4 0.38049334
# 21: ser 3 smp 1 tr 4 0.14170226
# 22: ser 1 smp 2 tr 4 0.63719962
# 23: ser 2 smp 2 tr 4 0.07100314
# 24: ser 3 smp 2 tr 4 0.11864134
请注意,这是多么容易,我们所有的维度标签都逐渐变成了长格式。 data.tables 的花里胡哨之一是能够在 data.tables 之间进行索引合并(很像 MySQL 索引连接)。所以在这里,我们将这样做以将class 绑定到我们的数据:
dt <- dt.raw[J(trial.labs, class=trial.class)] # on the fly mapping of trials to class
# tr ser smp value class
# 1: Trial 1 ser 1 smp 1 0.9648542 A
# 2: Trial 1 ser 2 smp 1 0.7285704 A
# 3: Trial 1 ser 3 smp 1 0.3142587 A
# 4: Trial 1 ser 1 smp 2 0.4134501 A
# 5: Trial 1 ser 2 smp 2 0.1393077 A
# ---
# 20: Trial 4 ser 2 smp 1 0.2432201 A
# 21: Trial 4 ser 3 smp 1 0.2671743 A
# 22: Trial 4 ser 1 smp 2 0.5160964 A
# 23: Trial 4 ser 2 smp 2 0.7702306 A
# 24: Trial 4 ser 3 smp 2 0.8568685 A
需要了解的几点:
-
J 从向量创建 data.table
- 尝试将一个
data.table 的行与另一个数据表进行子集化(即使用data.table 作为[.data.table 中大括号后的第一个参数)导致data.table 左连接(用MySQL 的说法)外部在这种情况下,将表(在这种情况下为dt)复制到内部表(由J 即时创建的表)。连接是在外部data.table 的key 列上完成的,您可能已经注意到我们在前面的melt/data.table 转换步骤中定义的。
您必须阅读文档才能完全理解发生了什么,但认为J(trial.labs, class=trial.class) 实际上等同于使用data.table(trial.labs, class=trial.class) 创建data.table,除了J 仅在[.data.table 内部使用时有效.
所以现在,通过一个简单的步骤,我们将类数据附加到值上。同样,如果您需要矩阵代数,请先对数组进行操作,然后用两三个简单的命令切换回长格式。正如 cmets 中所述,您可能不希望在长格式和数组格式之间来回切换,除非您有充分的理由这样做。
一旦内容在data.table 中,您就可以很容易地对数据进行分组/聚合(类似于拆分-应用-组合样式的概念)。假设我们要获取每个class-sample 组合的汇总统计信息:
dt[, as.list(summary(value)), by=list(class, smp)]
# class smp Min. 1st Qu. Median Mean 3rd Qu. Max.
# 1: A smp 1 0.08324 0.2537 0.3143 0.4708 0.7286 0.9649
# 2: A smp 2 0.10130 0.1609 0.5161 0.4749 0.6894 0.8569
# 3: B smp 1 0.14050 0.3089 0.4773 0.5049 0.6872 0.8970
# 4: B smp 2 0.08294 0.1196 0.1562 0.3818 0.5313 0.9063
在这里,我们只给 data.table 一个表达式 (as.list(summary(value))) 来评估每个数据的 class、smp 子集(如 by 表达式中指定的那样)。我们需要as.list,以便data.table 将结果重新组合为列。
您可以轻松计算类/样本/试验/系列变量的任意组合的矩(例如list(mean(value), var(value), (value - mean(value))^3)。
如果您想对数据进行简单的转换,使用data.table 非常容易:
dt[, value:=value * 10] # modify in place with `:=`, very efficient
dt[1:2] # see, `value` now 10x
# tr ser smp value class
# 1: Trial 1 ser 1 smp 1 9.648542 A
# 2: Trial 1 ser 2 smp 1 7.285704 A
这是就地转换,因此没有内存副本,因此速度很快。通常data.table 会尝试尽可能高效地使用内存,因此这是进行此类分析的最快方法之一。
从长格式绘图
ggplot 非常适合以长格式绘制数据。我不会详细介绍正在发生的事情,但希望这些图片能让您了解您可以做什么
library(ggplot2)
ggplot(data=dt, aes(x=ser, y=smp, color=class, size=value)) +
geom_point() +
facet_wrap( ~ tr)
ggplot(data=dt, aes(x=tr, y=value, fill=class)) +
geom_bar(stat="identity") +
facet_grid(smp ~ ser)
ggplot(data=dt, aes(x=tr, y=paste(ser, smp))) +
geom_tile(aes(fill=value)) +
geom_point(aes(shape=class), size=5) +
scale_fill_gradient2(low="yellow", high="blue", midpoint=median(dt$value))
数据表 -> 数组 -> 数据表
首先我们需要将acast(来自包reshape2)我们的数据表返回到一个数组中:
arr.2 <- acast(dt, ser ~ smp ~ tr, value.var="value")
dimnames(arr.2) <- dimnames(arr) # unfortunately `acast` doesn't preserve dimnames properly
# , , tr = Trial 1
# smp
# ser smp 1 smp 2
# ser 1 9.648542 4.134501
# ser 2 7.285704 1.393077
# ser 3 3.142587 1.012979
# ... omitted 3 trials ...
此时,arr.2 看起来就像 arr 所做的一样,除了值乘以 10。请注意,我们必须删除 class 列。现在,让我们做一些简单的矩阵代数
shuff.mat <- matrix(c(0, 1, 1, 0), nrow=2) # re-order columns
for(i in 1:dim(arr.2)[3]) arr.2[, , i] <- arr.2[, , i] %*% shuff.mat
现在,让我们回到 melt 的长格式。注意key 参数:
dt.2 <- data.table(melt(arr.2, value.name="new.value"), key=c("tr", "ser", "smp"))
最后,让我们重新加入dt 和dt.2。在这里你需要小心。 data.table 的行为是,如果外表没有键,内表将根据内表的所有键连接到外表。如果内表有键,data.table 将键连接键。这是一个问题,因为我们预期的外部表 dt 已经在之前的 tr 上有一个键,所以我们的连接将只发生在该列上。因此,我们需要将键放在外表上,或者重置键(我们在这里选择了后者):
setkey(dt, tr, ser, smp)
dt[dt.2]
# tr ser smp value class new.value
# 1: Trial 1 ser 1 smp 1 9.648542 A 4.134501
# 2: Trial 1 ser 1 smp 2 4.134501 A 9.648542
# 3: Trial 1 ser 2 smp 1 7.285704 A 1.393077
# 4: Trial 1 ser 2 smp 2 1.393077 A 7.285704
# 5: Trial 1 ser 3 smp 1 3.142587 A 1.012979
# ---
# 20: Trial 4 ser 1 smp 2 5.160964 A 5.867905
# 21: Trial 4 ser 2 smp 1 2.432201 A 7.702306
# 22: Trial 4 ser 2 smp 2 7.702306 A 2.432201
# 23: Trial 4 ser 3 smp 1 2.671743 A 8.568685
# 24: Trial 4 ser 3 smp 2 8.568685 A 2.671743
注意data.table通过匹配键列进行连接,即-通过将外部表的第一个键列匹配到内部表的第一列/键,第二个到第二个,依此类推,不考虑列名(有一个 FR here)。如果您的表/键的顺序不同(如果您注意到的话,这里就是这种情况),您需要重新排序您的列或确保两个表在您想要的列上具有相同顺序的键(我们在这里做了什么)。列顺序不正确的原因是因为我们添加类的第一个连接,它在tr 上连接并导致该列成为data.table 中的第一个。