在R中进行探索性分析的重复试验中积累的分类标签构建多变量数据的正确方法是什么?我不想回到MATLAB。
我喜欢R的分析函数和语法(以及令人惊叹的情节)比MATLAB更好,并且一直在努力重构我的东西。但是,我一直对工作中数据的组织方式感到困惑。
我通常使用多次试验重复的多变量时间序列,这些试验存储在SERIESxSAMPLESxTRIALS的大矩阵 rank-3 tensor 多维数组中。这偶尔适用于一些不错的线性代数东西,但是当涉及另一个变量,即CLASS时,它是笨拙的。通常,类标签存储在另一个维度为1x TRIALS
的向量中。
在分析方面,我基本上尽量少绘图,因为需要做很多工作才能将一个非常好的情节汇总在一起,教你很多关于MATLAB中的数据。 (I'm not the only one who feels this way)。
在R中我一直坚持尽可能接近MATLAB结构,但是当试图保持类标签分离时,事情变得非常复杂;即使我只使用它们的属性,我也必须继续将标签传递给函数。所以我所做的是通过CLASS将数组分成数组列表。这增加了我所有apply()
函数的复杂性,但在保持一致性(和错误)方面似乎是值得的。
另一方面,R对于张量/多维数组似乎并不友好。只是为了与他们合作,你需要抓住abind
库。关于multivariate analysis, like this example的文档似乎在假设您有一个巨大的2-D数据点表(如一些长中世纪滚动数据框)的情况下运行,并且没有提到如何获取'从那里我就是。
一旦我对绘制的数据进行绘图和分类,这不是一个大问题,因为到那时我已经逐渐使用了像TRIALSxFEATURES(melt
这样的形状的数据框友好结构。这很多)。另一方面,如果我想快速生成探索阶段的散点图矩阵或latticist直方图集(即统计矩,分离,类/方差,直方图等),我必须停下来弄清楚如何我将apply()
这些巨大的多维数组转换为这些库所理解的东西。
如果我继续在丛林中捣乱,为此提出临时解决方案,我要么永远不会变得更好,要么我最终将以我自己奇怪的巫术方式做到这一点对任何人都有意义。
那么正确的方法是如何构建多元数据的方法,在R的探索性分析中,通过重复试验积累分类标签?拜托,我不想回到MATLAB。
奖励:我倾向于在多个主题的相同数据结构上重复这些分析。有没有比将代码块包装到for
循环更好的通用方法?
答案 0 :(得分:19)
正如已经指出的,许多更强大的分析和可视化工具依赖于长格式的数据。当然,对于受益于矩阵代数的转换,您应该将数据保存在数组中,但只要您想要对数据的子集进行并行分析,或者根据数据中的因子绘制内容,您真的想要{{1 }}
以下是一个示例,可帮助您开始使用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
并将其转换为melt
(注意data.table
对melt
进行操作,这基本上是data.frames
s ss bells&amp;哨声,所以我们必须首先融化,然后转换为data.table
):
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
之间进行索引合并(很像MySQL索引连接)。所以在这里,我们将把data.table
绑定到我们的数据:
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
)连接到内部表(由dt
动态创建的表)。联接在外部J
的{{1}}列上完成,您可能已经注意到我们之前在key
/ data.table
转换步骤中定义了这一点。 / LI>
醇>
您必须阅读文档才能完全了解正在发生的事情,但请注意melt
实际上等同于使用data.table
创建J(trial.labs, class=trial.class)
,除data.table
外,data.table(trial.labs, class=trial.class)
只能在J
内使用。
现在,通过一个简单的步骤,我们将类数据附加到值。同样,如果您需要矩阵代数,请先对您的阵列进行操作,然后在两到三个简单命令中切换回长格式。正如评论中所指出的那样,除非你有充分的理由这样做,否则你可能不希望从长格式转换为数组格式。
一旦事情进入[.data.table
,您就可以非常轻松地对数据进行分组/聚合(类似于split-apply-combine样式的概念)。假设我们想要获取每个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
子集(在{{1}中指定)表达)。我们需要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 ...
一样,除了值乘以10.注意我们必须删除arr.2
列。现在,让我们做一些琐碎的矩阵代数
arr
现在,让我们回到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
通过匹配键列执行连接,即 - 通过将外部表的第一个键列与内部表的第一个列/键匹配,第二个到第二个,依此类推,不考虑列名(有{FR here)。如果您的表/键的顺序不同(如果您注意到的话,就像这里的情况一样),您需要重新排序列或确保两个表在所需的列上都有相同顺序的列(我们在这里做了什么)。列未按正确顺序排列的原因是我们在第一次加入时添加了类,该类加入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
中的第一个。< / p>
答案 1 :(得分:11)
从@ BrodieG的优秀答案开始,我认为您可能会发现查看dplyr::tbl_cube
提供的新功能很有用。这本质上是一个多维对象,您可以从数组列表中轻松创建(正如您当前使用的那样),它具有一些非常好的子集化,过滤和汇总功能,其中(重要的是,我认为)在整个“立方体“视图和”表格“数据视图。
require(dplyr)
几点需要注意:
这是一个早期版本:所有与之相关的问题 建议此版本在加载dplyr时卸载plyr
以下是使用另一个答案中定义的arr
的示例:
# using arr from previous example
# we can convert it simply into a tbl_cube
arr.cube<-as.tbl_cube(arr)
arr.cube
#Source: local array [24 x 3]
#D: ser [chr, 3]
#D: smp [chr, 2]
#D: tr [chr, 4]
#M: arr [dbl[3,2,4]]
请注意,D表示尺寸和M尺寸,您可以根据需要添加任意尺寸。
您可以通过将数据作为data.frame(如果以后需要功能和性能优势,只需将其转换为data.table)轻松制作数据表格。
head(as.data.frame(arr.cube))
# ser smp tr arr
#1 ser 1 smp 1 tr 1 0.6656456
#2 ser 2 smp 1 tr 1 0.6181301
#3 ser 3 smp 1 tr 1 0.7335676
#4 ser 1 smp 2 tr 1 0.9444435
#5 ser 2 smp 2 tr 1 0.8977054
#6 ser 3 smp 2 tr 1 0.9361929
显然,您可以为每个操作压缩所有数据,但这对性能和实用性有很多影响。我认为这个软件包的真正好处是,您可以在将多维数据集转换为ggplot友好的表格格式之前,为您所需的数据“预先挖掘”多维数据集,例如:简单过滤只返回系列1:
arr.cube.filtered<-filter(arr.cube,ser=="ser 1")
as.data.frame(arr.cube.filtered)
# ser smp tr arr
#1 ser 1 smp 1 tr 1 0.6656456
#2 ser 1 smp 2 tr 1 0.9444435
#3 ser 1 smp 1 tr 2 0.4331116
#4 ser 1 smp 2 tr 2 0.3916376
#5 ser 1 smp 1 tr 3 0.4669228
#6 ser 1 smp 2 tr 3 0.8942300
#7 ser 1 smp 1 tr 4 0.2054326
#8 ser 1 smp 2 tr 4 0.1006973
tbl_cube目前适用于dplyr
个功能summarise()
,select()
,group_by()
和filter()
。有用的,您可以使用%.%
运算符将它们链接在一起。
对于其他示例,我将使用内置的nasa
tbl_cube对象,该对象包含大量气象数据(并演示多个维度和度量):
nasa
#Source: local array [41,472 x 4]
#D: lat [dbl, 24]
#D: long [dbl, 24]
#D: month [int, 12]
#D: year [int, 6]
#M: cloudhigh [dbl[24,24,12,6]]
#M: cloudlow [dbl[24,24,12,6]]
#M: cloudmid [dbl[24,24,12,6]]
#M: ozone [dbl[24,24,12,6]]
#M: pressure [dbl[24,24,12,6]]
#M: surftemp [dbl[24,24,12,6]]
#M: temperature [dbl[24,24,12,6]]
所以这里有一个示例显示从多维数据集中撤回已修改数据的子集是多么容易,然后将其展平以便适合绘图:
plot_data<-as.data.frame( # as.data.frame so we can see the data
filter(nasa,long<(-70)) %.% # filter long < (-70) (arbitrary!)
group_by(lat,long) %.% # group by lat/long combo
summarise(p.max=max(pressure), # create summary measures for each group
o.avg=mean(ozone),
c.all=(cloudhigh+cloudlow+cloudmid)/3)
)
head(plot_data)
# lat long p.max o.avg c.all
#1 36.20000 -113.8 975 310.7778 22.66667
#2 33.70435 -113.8 975 307.0833 21.33333
#3 31.20870 -113.8 990 300.3056 19.50000
#4 28.71304 -113.8 1000 290.3056 16.00000
#5 26.21739 -113.8 1000 282.4167 14.66667
#6 23.72174 -113.8 1000 275.6111 15.83333
遗憾的是mutate()
函数尚未针对tbl_cube
实现,但看起来这只是(不多)时间问题。但是,您可以在表格结果中使用它(以及在立方体上工作的所有其他函数) - 使用完全相同的表示法。例如:
plot_data.mod<-filter(plot_data,lat>25) %.% # filter out lat <=25
mutate(arb.meas=o.avg/p.max) # make a new column
head(plot_data.mod)
# lat long p.max o.avg c.all arb.meas
#1 36.20000 -113.8000 975 310.7778 22.66667 0.3187464
#2 33.70435 -113.8000 975 307.0833 21.33333 0.3149573
#3 31.20870 -113.8000 990 300.3056 19.50000 0.3033389
#4 28.71304 -113.8000 1000 290.3056 16.00000 0.2903056
#5 26.21739 -113.8000 1000 282.4167 14.66667 0.2824167
#6 36.20000 -111.2957 930 313.9722 20.66667 0.3376045
然后,您可以使用展平数据的优势与ggplot()
进行绘图:
# plot as you like:
ggplot(plot_data.mod) +
geom_point(aes(lat,long,size=c.all,color=c.all,shape=cut(p.max,6))) +
facet_grid( lat ~ long ) +
theme(axis.text.x = element_text(angle = 90, hjust = 1))
我不会在这里扩展使用data.table
,因为它在前一个答案中做得很好。显然使用data.table
有很多充分的理由 - 对于任何情况,你可以通过简单的data.frame转换返回一个:
data.table(as.data.frame(your_cube_name))
我认为另一件很棒的事情就是能够在你的多维数据集中添加度量(切片/场景/移位,无论你想要什么)。我认为这符合问题中描述的分析方法。这是一个带有arr.cube
的简单示例 - 添加一个额外的度量,它本身就是上一个度量的一个(公认的简单)函数。您可以通过语法 yourcube $mets[$...]
head(as.data.frame(arr.cube))
# ser smp tr arr
#1 ser 1 smp 1 tr 1 0.6656456
#2 ser 2 smp 1 tr 1 0.6181301
#3 ser 3 smp 1 tr 1 0.7335676
#4 ser 1 smp 2 tr 1 0.9444435
#5 ser 2 smp 2 tr 1 0.8977054
#6 ser 3 smp 2 tr 1 0.9361929
arr.cube$mets$arr.bump<-arr.cube$mets$arr*1.1 #arb modification!
head(as.data.frame(arr.cube))
# ser smp tr arr arr.bump
#1 ser 1 smp 1 tr 1 0.6656456 0.7322102
#2 ser 2 smp 1 tr 1 0.6181301 0.6799431
#3 ser 3 smp 1 tr 1 0.7335676 0.8069244
#4 ser 1 smp 2 tr 1 0.9444435 1.0388878
#5 ser 2 smp 2 tr 1 0.8977054 0.9874759
#6 ser 3 smp 2 tr 1 0.9361929 1.0298122
我尝试动态添加全新维度(使用 yourcube $dims[$...]
有效扩展现有多维数据集并使用 yourcube tempfilename<-gsub("[ :-]","",paste0("DBX",(Sys.time()),".cub"))
# save:
save(arr.cube,file=tempfilename)
# load:
load(file=tempfilename)
克隆或修改原始数据)发现这种行为有点不一致。无论如何最好避免这种情况,并在操作它之前先构建你的立方体。如果我到处都会让你发布。
显然,让解释器访问多维数据库的主要问题之一是有可能在不合时宜的击键时误操作它。所以我想早点经常坚持:
{{1}}
希望有所帮助!