如何在 R 中重新排序数组的第一个暗淡(不知道总暗淡)
How to reorder 1st dim of array in R (don't know total dims)
我有一个数组,我需要对其第一个维度进行子集化/索引/重新排序。例如:
arr <- array(1:24, dim=c(4,3,2))
arr[4:1,,]
简单,很有魅力。
但是,当我不确定数组有多少维时,有没有办法做到这一点?明确地说,我总是知道第一个维的大小(即,我知道 dim(arr)[1]
),我只是不知道 length(dim(arr))
。
我有一个丑陋且低效的解决方案。更简单方法的问题是我不知道如何使用 do.call
正确实现 [
的默认值。也许有人会看到并受到启发。
函数如下:
orderD1 <- function(x, ord){
dims <- dim(x)
ndim <- length(dims)
stopifnot(ndim>0)
if(ndim==1){
return(x[ord])
}
wl_i <- which(letters=="i")
dimLetters <- letters[wl_i:(wl_i+ndim-1)]
dimList <- structure(vector("list",ndim), .Names=dimLetters)
dimList[[1]] <- ord
for(i in 2:ndim){
dimList[[i]] <- 1:dims[i]
}
do.call("[",c(list(x=x),dimList))
}
这里是使用问题中示例的实现:
orderD1(arr, 4:1)
, , 1
[,1] [,2] [,3]
[1,] 4 8 12
[2,] 3 7 11
[3,] 2 6 10
[4,] 1 5 9
, , 2
[,1] [,2] [,3]
[1,] 16 20 24
[2,] 15 19 23
[3,] 14 18 22
[4,] 13 17 21
这是一个多么慢的例子...
library(microbenchmark)
microbenchmark(arr[4:1,,], orderD1(arr, 4:1), times=1E3)
Unit: nanoseconds
expr min lq mean median uq max neval
arr[4:1, , ] 864 1241 1445.876 1451 1596.0 17191 1000
orderD1(arr, 4:1) 52020 54061 56286.856 54909 56194.5 179363 1000
我很乐意接受更优雅/更紧凑的解决方案。
这是一种可能的方法,尽管它仍然有点慢。
do.call(`[`, c(list(arr, 4:1), lapply(dim(arr)[-1], seq_len)))
## , , 1
##
## [,1] [,2] [,3]
## [1,] 4 8 12
## [2,] 3 7 11
## [3,] 2 6 10
## [4,] 1 5 9
##
## , , 2
##
## [,1] [,2] [,3]
## [1,] 16 20 24
## [2,] 15 19 23
## [3,] 14 18 22
## [4,] 13 17 21
do.call
需要一个参数列表(如果未命名)将按照提供它们的顺序传递给指定的函数(在本例中为 [
)。
上面,我们传递了一个列表,list(arr, 4:1, 1:3, 1:2)
到[
,相当于做:`[`(arr, 4:1, 1:3, 1:2)
(反过来,相当于arr[4:1, 1:3, 1:2]
) .
时间:
microbenchmark(subset=arr[4:1,,],
jb=do.call(`[`, c(list(arr, 4:1), lapply(dim(arr)[-1], seq_len))),
times=1E3)
## Unit: microseconds
## expr min lq mean median uq max neval
## subset 1.140 1.711 1.765575 1.711 1.711 15.395 1000
## jb 9.693 10.834 11.464768 11.404 11.974 96.365 1000
(忽略绝对时间 - 我的系统目前处于紧张状态。)
因此,它花费的时间大约是简单子集的十倍。这里可能还有改进的余地,尽管正如@thelatemail 评论的那样,时间在更大的阵列上更具可比性。
编辑
根据@thelatemail 的建议,索引序列可以替换为 TRUE
,这样会加快速度。
do.call(`[`, c(list(arr, 4:1), rep(TRUE, length(dim(arr))-1)))
再次计时:
microbenchmark(subset=arr[4:1,,],
jb=do.call(`[`, c(list(arr, 4:1), lapply(dim(arr)[-1], seq_len))),
tlm=do.call(`[`, c(list(arr, 4:1), rep(TRUE, length(dim(arr)) - 1))),
times=1E3)
## Unit: microseconds
## expr min lq mean median uq max neval
## subset 1.140 1.711 2.146474 1.711 2.281 124.875 1000
## jb 10.834 11.974 13.455343 12.545 13.685 293.086 1000
## tlm 6.272 7.413 8.348592 7.983 8.553 95.795 1000
这是一个奇怪的选择。这个想法基于我曾经注意到的一个实现怪癖,即 R 似乎将 "missing" 函数参数表示为 symbols 且名称长度为零。这如此奇怪的原因之一是 R 通常不允许您创建具有零长度名称的符号:
as.symbol('');
## Error in as.symbol("") : attempt to use zero-length variable name
但经过一番摸索,我发现你可以通过访问包含 "missing" 参数的表达式的解析树,并索引出包含"missing" 参数。这是您从这件事中获得的一些奇怪行为的演示:
substitute(x[]); ## parse tree involving missing argument
## x[]
as.list(substitute(x[])); ## show list representation; third component is the guy
## [[1]]
## `[`
##
## [[2]]
## x
##
## [[3]]
##
##
substitute(x[])[[3]]; ## prints nothing!
##
(function(x) c(typeof(x),mode(x),class(x)))(substitute(x[])[[3]]); ## it's a symbol alright
## [1] "symbol" "name" "name"
as.character(substitute(x[])[[3]]); ## gets the name of the symbol: the empty string!
## [1] ""
i.dont.exist <- substitute(x[])[[3]]; ## store in variable
i.dont.exist; ## wha??
## Error: argument "i.dont.exist" is missing, with no default
无论如何,这是我们可以针对 OP 问题得出的解决方案:
arr <- array(1:24,4:2);
do.call(`[`,c(list(arr,4:1),rep(list(substitute(x[])[[3]]),length(dim(arr))-1)));
## , , 1
##
## [,1] [,2] [,3]
## [1,] 4 8 12
## [2,] 3 7 11
## [3,] 2 6 10
## [4,] 1 5 9
##
## , , 2
##
## [,1] [,2] [,3]
## [1,] 16 20 24
## [2,] 15 19 23
## [3,] 14 18 22
## [4,] 13 17 21
##
我希望它能胜过所有其他解决方案,但是@thelatemail,你赢了这一轮: 啊哈!我意识到我们可以预先计算一个空符号列表(将一个空符号本身存储在一个变量中,即列表中的 not 是不可用的,如我上面所示)和 rep()
解决方案中的列表,而不是在解决方案的每次调用中产生 substitute()
的所有开销来解析虚拟表达式。看看表演:
straight <- function() arr[4:1,,];
jb <- function() do.call(`[`,c(list(arr,4:1),lapply(dim(arr)[-1],seq_len)));
tlm <- function() do.call(`[`,c(list(arr,4:1),rep(TRUE,length(dim(arr))-1)));
orderD1 <- function(x,ord) { dims <- dim(x); ndim <- length(dims); stopifnot(ndim>0); if (ndim==1) return(x[ord]); wl_i <- which(letters=="i"); dimLetters <- letters[wl_i:(wl_i+ndim-1)]; dimList <- structure(vector("list",ndim),.Names=dimLetters); dimList[[1]] <- ord; for (i in 2:ndim) dimList[[i]] <- 1:dims[i]; do.call("[",c(list(x=x),dimList)); };
rbatt <- function() orderD1(arr,4:1);
bgoldst <- function() do.call(`[`,c(list(arr,4:1),rep(list(substitute(x[])[[3]]),length(dim(arr))-1)));
ls0 <- list(substitute(x[])[[3]]);
ls0;
## [[1]]
##
##
bgoldst2 <- function() do.call(`[`,c(list(arr,4:1),rep(ls0,length(dim(arr))-1)));
microbenchmark(straight(),jb(),tlm(),rbatt(),bgoldst(),bgoldst2(),times=1e5);
## Unit: nanoseconds
## expr min lq mean median uq max neval
## straight() 428 856 1161.038 856 1284 998142 1e+05
## jb() 4277 5988 7136.534 6843 7271 1629357 1e+05
## tlm() 2566 3850 4622.668 4277 4705 1704196 1e+05
## rbatt() 24804 28226 31975.583 29509 31219 34970873 1e+05
## bgoldst() 3421 4705 5601.300 5132 5560 1918878 1e+05
## bgoldst2() 2566 3850 4533.383 4277 4705 1034065 1e+05
刚刚发现有一种更简单的方法来获取空符号,这似乎一直可用:
substitute();
##
我的 substitute(x[])[[3]]
技巧现在看起来有点愚蠢。
出于好奇,我直接使用 substitute()
与其他解决方案进行了基准测试,与 bgoldst2()
相比,它会产生轻微的性能成本,使其略低于 tlm()
:
bgoldst3 <- function() do.call(`[`,c(list(arr,4:1),rep(list(substitute()),length(dim(arr))-1)));
microbenchmark(straight(),jb(),tlm(),rbatt(),bgoldst(),bgoldst2(),bgoldst3(),times=1e5);
## Unit: nanoseconds
## expr min lq mean median uq max neval
## straight() 428 856 1069.340 856 1284 850603 1e+05
## jb() 4277 5988 6916.899 6416 7270 2978180 1e+05
## tlm() 2566 3849 4307.979 4277 4704 3138122 1e+05
## rbatt() 24377 28226 30882.666 29508 30364 36768360 1e+05
## bgoldst() 2994 4704 5165.019 5132 5560 2050171 1e+05
## bgoldst2() 2566 3849 4232.816 4277 4278 1085813 1e+05
## bgoldst3() 2566 3850 4545.508 4277 4705 1004131 1e+05
我有一个数组,我需要对其第一个维度进行子集化/索引/重新排序。例如:
arr <- array(1:24, dim=c(4,3,2))
arr[4:1,,]
简单,很有魅力。
但是,当我不确定数组有多少维时,有没有办法做到这一点?明确地说,我总是知道第一个维的大小(即,我知道 dim(arr)[1]
),我只是不知道 length(dim(arr))
。
我有一个丑陋且低效的解决方案。更简单方法的问题是我不知道如何使用 do.call
正确实现 [
的默认值。也许有人会看到并受到启发。
函数如下:
orderD1 <- function(x, ord){
dims <- dim(x)
ndim <- length(dims)
stopifnot(ndim>0)
if(ndim==1){
return(x[ord])
}
wl_i <- which(letters=="i")
dimLetters <- letters[wl_i:(wl_i+ndim-1)]
dimList <- structure(vector("list",ndim), .Names=dimLetters)
dimList[[1]] <- ord
for(i in 2:ndim){
dimList[[i]] <- 1:dims[i]
}
do.call("[",c(list(x=x),dimList))
}
这里是使用问题中示例的实现:
orderD1(arr, 4:1)
, , 1
[,1] [,2] [,3]
[1,] 4 8 12
[2,] 3 7 11
[3,] 2 6 10
[4,] 1 5 9
, , 2
[,1] [,2] [,3]
[1,] 16 20 24
[2,] 15 19 23
[3,] 14 18 22
[4,] 13 17 21
这是一个多么慢的例子...
library(microbenchmark)
microbenchmark(arr[4:1,,], orderD1(arr, 4:1), times=1E3)
Unit: nanoseconds
expr min lq mean median uq max neval
arr[4:1, , ] 864 1241 1445.876 1451 1596.0 17191 1000
orderD1(arr, 4:1) 52020 54061 56286.856 54909 56194.5 179363 1000
我很乐意接受更优雅/更紧凑的解决方案。
这是一种可能的方法,尽管它仍然有点慢。
do.call(`[`, c(list(arr, 4:1), lapply(dim(arr)[-1], seq_len)))
## , , 1
##
## [,1] [,2] [,3]
## [1,] 4 8 12
## [2,] 3 7 11
## [3,] 2 6 10
## [4,] 1 5 9
##
## , , 2
##
## [,1] [,2] [,3]
## [1,] 16 20 24
## [2,] 15 19 23
## [3,] 14 18 22
## [4,] 13 17 21
do.call
需要一个参数列表(如果未命名)将按照提供它们的顺序传递给指定的函数(在本例中为 [
)。
上面,我们传递了一个列表,list(arr, 4:1, 1:3, 1:2)
到[
,相当于做:`[`(arr, 4:1, 1:3, 1:2)
(反过来,相当于arr[4:1, 1:3, 1:2]
) .
时间:
microbenchmark(subset=arr[4:1,,],
jb=do.call(`[`, c(list(arr, 4:1), lapply(dim(arr)[-1], seq_len))),
times=1E3)
## Unit: microseconds
## expr min lq mean median uq max neval
## subset 1.140 1.711 1.765575 1.711 1.711 15.395 1000
## jb 9.693 10.834 11.464768 11.404 11.974 96.365 1000
(忽略绝对时间 - 我的系统目前处于紧张状态。)
因此,它花费的时间大约是简单子集的十倍。这里可能还有改进的余地,尽管正如@thelatemail 评论的那样,时间在更大的阵列上更具可比性。
编辑
根据@thelatemail 的建议,索引序列可以替换为 TRUE
,这样会加快速度。
do.call(`[`, c(list(arr, 4:1), rep(TRUE, length(dim(arr))-1)))
再次计时:
microbenchmark(subset=arr[4:1,,],
jb=do.call(`[`, c(list(arr, 4:1), lapply(dim(arr)[-1], seq_len))),
tlm=do.call(`[`, c(list(arr, 4:1), rep(TRUE, length(dim(arr)) - 1))),
times=1E3)
## Unit: microseconds
## expr min lq mean median uq max neval
## subset 1.140 1.711 2.146474 1.711 2.281 124.875 1000
## jb 10.834 11.974 13.455343 12.545 13.685 293.086 1000
## tlm 6.272 7.413 8.348592 7.983 8.553 95.795 1000
这是一个奇怪的选择。这个想法基于我曾经注意到的一个实现怪癖,即 R 似乎将 "missing" 函数参数表示为 symbols 且名称长度为零。这如此奇怪的原因之一是 R 通常不允许您创建具有零长度名称的符号:
as.symbol('');
## Error in as.symbol("") : attempt to use zero-length variable name
但经过一番摸索,我发现你可以通过访问包含 "missing" 参数的表达式的解析树,并索引出包含"missing" 参数。这是您从这件事中获得的一些奇怪行为的演示:
substitute(x[]); ## parse tree involving missing argument
## x[]
as.list(substitute(x[])); ## show list representation; third component is the guy
## [[1]]
## `[`
##
## [[2]]
## x
##
## [[3]]
##
##
substitute(x[])[[3]]; ## prints nothing!
##
(function(x) c(typeof(x),mode(x),class(x)))(substitute(x[])[[3]]); ## it's a symbol alright
## [1] "symbol" "name" "name"
as.character(substitute(x[])[[3]]); ## gets the name of the symbol: the empty string!
## [1] ""
i.dont.exist <- substitute(x[])[[3]]; ## store in variable
i.dont.exist; ## wha??
## Error: argument "i.dont.exist" is missing, with no default
无论如何,这是我们可以针对 OP 问题得出的解决方案:
arr <- array(1:24,4:2);
do.call(`[`,c(list(arr,4:1),rep(list(substitute(x[])[[3]]),length(dim(arr))-1)));
## , , 1
##
## [,1] [,2] [,3]
## [1,] 4 8 12
## [2,] 3 7 11
## [3,] 2 6 10
## [4,] 1 5 9
##
## , , 2
##
## [,1] [,2] [,3]
## [1,] 16 20 24
## [2,] 15 19 23
## [3,] 14 18 22
## [4,] 13 17 21
##
我希望它能胜过所有其他解决方案,但是@thelatemail,你赢了这一轮: 啊哈!我意识到我们可以预先计算一个空符号列表(将一个空符号本身存储在一个变量中,即列表中的 not 是不可用的,如我上面所示)和 rep()
解决方案中的列表,而不是在解决方案的每次调用中产生 substitute()
的所有开销来解析虚拟表达式。看看表演:
straight <- function() arr[4:1,,];
jb <- function() do.call(`[`,c(list(arr,4:1),lapply(dim(arr)[-1],seq_len)));
tlm <- function() do.call(`[`,c(list(arr,4:1),rep(TRUE,length(dim(arr))-1)));
orderD1 <- function(x,ord) { dims <- dim(x); ndim <- length(dims); stopifnot(ndim>0); if (ndim==1) return(x[ord]); wl_i <- which(letters=="i"); dimLetters <- letters[wl_i:(wl_i+ndim-1)]; dimList <- structure(vector("list",ndim),.Names=dimLetters); dimList[[1]] <- ord; for (i in 2:ndim) dimList[[i]] <- 1:dims[i]; do.call("[",c(list(x=x),dimList)); };
rbatt <- function() orderD1(arr,4:1);
bgoldst <- function() do.call(`[`,c(list(arr,4:1),rep(list(substitute(x[])[[3]]),length(dim(arr))-1)));
ls0 <- list(substitute(x[])[[3]]);
ls0;
## [[1]]
##
##
bgoldst2 <- function() do.call(`[`,c(list(arr,4:1),rep(ls0,length(dim(arr))-1)));
microbenchmark(straight(),jb(),tlm(),rbatt(),bgoldst(),bgoldst2(),times=1e5);
## Unit: nanoseconds
## expr min lq mean median uq max neval
## straight() 428 856 1161.038 856 1284 998142 1e+05
## jb() 4277 5988 7136.534 6843 7271 1629357 1e+05
## tlm() 2566 3850 4622.668 4277 4705 1704196 1e+05
## rbatt() 24804 28226 31975.583 29509 31219 34970873 1e+05
## bgoldst() 3421 4705 5601.300 5132 5560 1918878 1e+05
## bgoldst2() 2566 3850 4533.383 4277 4705 1034065 1e+05
刚刚发现有一种更简单的方法来获取空符号,这似乎一直可用:
substitute();
##
我的 substitute(x[])[[3]]
技巧现在看起来有点愚蠢。
出于好奇,我直接使用 substitute()
与其他解决方案进行了基准测试,与 bgoldst2()
相比,它会产生轻微的性能成本,使其略低于 tlm()
:
bgoldst3 <- function() do.call(`[`,c(list(arr,4:1),rep(list(substitute()),length(dim(arr))-1)));
microbenchmark(straight(),jb(),tlm(),rbatt(),bgoldst(),bgoldst2(),bgoldst3(),times=1e5);
## Unit: nanoseconds
## expr min lq mean median uq max neval
## straight() 428 856 1069.340 856 1284 850603 1e+05
## jb() 4277 5988 6916.899 6416 7270 2978180 1e+05
## tlm() 2566 3849 4307.979 4277 4704 3138122 1e+05
## rbatt() 24377 28226 30882.666 29508 30364 36768360 1e+05
## bgoldst() 2994 4704 5165.019 5132 5560 2050171 1e+05
## bgoldst2() 2566 3849 4232.816 4277 4278 1085813 1e+05
## bgoldst3() 2566 3850 4545.508 4277 4705 1004131 1e+05