我有一个非常大的数据集,所以我试图用下面的一个小例子来总结我的问题。
假设我有一个名为X的3X3矩阵,列名为a,b和c。
X = (1, 10, 0.1,
2, 20, 0.2,
3, 30, 0.3)
其中a = c(1, 2, 3)
给出重复次数,b = c(10, 20, 30)
给出重复的实际值,c = c(0.1, 0.2, 0.3)
给出值,以填写{{1}中的次数}小于4(矩阵Y的数字列)。
我的目标是生成一个3X4矩阵Y,它应该是这样的
a
我知道可能有很多方法可以做这个例子,但由于我的真实数据非常大(X有一百万行,而Y有480列),我真的必须这样做而没有循环(如480次迭代) )。我已尝试使用函数Y = (10, 0.1, 0.1, 0.1,
20, 20, 0.2, 0.2,
30, 30, 30, 0.3)
,但仍无法执行此操作。
答案 0 :(得分:4)
输出矩阵的每一行都可以通过对rep
函数的单次调用来计算,从而使整个操作成为一个单行:
t(apply(X, 1, function(x) rep(x[2:3], c(x[1], 4-x[1]))))
# [,1] [,2] [,3] [,4]
# [1,] 10 0.1 0.1 0.1
# [2,] 20 20.0 0.2 0.2
# [3,] 30 30.0 30.0 0.3
你说你正在计划创建一个1e6 x 480矩阵,它有望适合你系统的内存。但是,如果不耗尽系统内存,你可能无法将其推得太大。
答案 1 :(得分:1)
这并不容易,但我找到了一种方法来完成这项任务,使用rep()
的单一矢量化调用,以及一些脚手架代码:
XR <- 3;
YC <- 4;
X <- matrix(c(1:XR%%(YC+1),seq(10,by=10,length.out=XR),seq(0.1,by=0.1,length.out=XR)),XR,dimnames=list(NULL,c('rep','val','fill')));
X;
## rep val fill
## [1,] 1 10 0.1
## [2,] 2 20 0.2
## [3,] 3 30 0.3
Y <- matrix(rep(t(X[,c('val','fill')]),times=c(rbind(X[,'rep'],YC-X[,'rep']))),XR,byrow=T);
Y;
## [,1] [,2] [,3] [,4]
## [1,] 10 0.1 0.1 0.1
## [2,] 20 20.0 0.2 0.2
## [3,] 30 30.0 30.0 0.3
(小点:我选择将列名rep val fill
分配给X
,而不是问题中指定的a b c
,并且在索引时我在解决方案中使用了这些列名{ {1}}(而不是使用数字索引),因为我通常更喜欢尽可能地最大化人类可读性,但这个细节在解决方案的正确性和性能方面可以忽略不计。)
这实际上比@ josilber的解决方案具有显着的性能优势,因为他使用X
内部循环遍历矩阵的行(传统上称为&#34;隐藏循环&#34; in R-speak),而我的解决方案的核心是对apply()
的单个矢量化调用。我不是这样说来敲@josilber的解决方案,这是一个很好的解决方案(我甚至给了他一个upvote!);它不是解决这个问题的最佳解决方案。
以下是使用您在问题中指出的重要参数来演示性能优势:
rep()
只是为了证明@josilber和我得到完全相同的结果,即使对于这个大输入:
XR <- 1e6;
YC <- 480;
X <- matrix(c(1:XR%%(YC+1),seq(10,by=10,length.out=XR),seq(0.1,by=0.1,length.out=XR)),XR,dimnames=list(NULL,c('rep','val','fill')));
X;
## rep val fill
## [1,] 1 10 0.1
## [2,] 2 20 0.2
## [3,] 3 30 0.3
## [4,] 4 40 0.4
## [5,] 5 50 0.5
## [6,] 6 60 0.6
## [7,] 7 70 0.7
## [8,] 8 80 0.8
## [9,] 9 90 0.9
## [10,] 10 100 1.0
## [11,] 11 110 1.1
## [12,] 12 120 1.2
## [13,] 13 130 1.3
##
## ... (snip) ...
##
## [477,] 477 4770 47.7
## [478,] 478 4780 47.8
## [479,] 479 4790 47.9
## [480,] 480 4800 48.0
## [481,] 0 4810 48.1
## [482,] 1 4820 48.2
## [483,] 2 4830 48.3
## [484,] 3 4840 48.4
## [485,] 4 4850 48.5
## [486,] 5 4860 48.6
## [487,] 6 4870 48.7
## [488,] 7 4880 48.8
## [489,] 8 4890 48.9
## [490,] 9 4900 49.0
## [491,] 10 4910 49.1
## [492,] 11 4920 49.2
##
## ... (snip) ...
##
## [999986,] 468 9999860 99998.6
## [999987,] 469 9999870 99998.7
## [999988,] 470 9999880 99998.8
## [999989,] 471 9999890 99998.9
## [999990,] 472 9999900 99999.0
## [999991,] 473 9999910 99999.1
## [999992,] 474 9999920 99999.2
## [999993,] 475 9999930 99999.3
## [999994,] 476 9999940 99999.4
## [999995,] 477 9999950 99999.5
## [999996,] 478 9999960 99999.6
## [999997,] 479 9999970 99999.7
## [999998,] 480 9999980 99999.8
## [999999,] 0 9999990 99999.9
## [1e+06,] 1 10000000 100000.0
josilber <- function() t(apply(X,1,function(x) rep(x[2:3],c(x[1],YC-x[1]))));
bgoldst <- function() matrix(rep(t(X[,c('val','fill')]),times=c(rbind(X[,'rep'],YC-X[,'rep']))),XR,byrow=T);
system.time({ josilber(); });
## user system elapsed
## 65.719 3.828 71.623
system.time({ josilber(); });
## user system elapsed
## 60.375 2.609 66.724
system.time({ bgoldst(); });
## user system elapsed
## 5.422 0.593 6.033
system.time({ bgoldst(); });
## user system elapsed
## 5.203 0.797 6.002
现在我将尝试解释解决方案的工作原理。为了解释,我将使用以下输入:
identical(bgoldst(),josilber());
## [1] TRUE
解决方案是:
XR <- 6;
YC <- 4;
X <- matrix(c(1:XR%%(YC+1),seq(10,by=10,length.out=XR),seq(0.1,by=0.1,length.out=XR)),XR,dimnames=list(NULL,c('rep','val','fill')));
X;
## rep val fill
## [1,] 1 10 0.1
## [2,] 2 20 0.2
## [3,] 3 30 0.3
## [4,] 4 40 0.4
## [5,] 0 50 0.5
## [6,] 1 60 0.6
在高层次上,解决方案围绕形成单个向量,该向量组合Y <- matrix(rep(t(X[,c('val','fill')]),times=c(rbind(X[,'rep'],YC-X[,'rep']))),XR,byrow=T);
Y;
## [,1] [,2] [,3] [,4]
## [1,] 10.0 0.1 0.1 0.1
## [2,] 20.0 20.0 0.2 0.2
## [3,] 30.0 30.0 30.0 0.3
## [4,] 40.0 40.0 40.0 40.0
## [5,] 0.5 0.5 0.5 0.5
## [6,] 60.0 0.6 0.6 0.6
和val
向量,然后以某种方式重复组合向量,然后构建新的矩阵结果。
重复步骤可以使用fill
的单个调用完成,因为它支持向量化重复计数。换句话说,对于给定的向量输入rep()
,它可以采用x
的向量输入,该向量输入指定重复times
的每个元素的次数。因此,挑战只是构建适当的x
和x
参数。
因此,解决方案首先提取times
的{{1}}和val
列:
fill
正如您所看到的,由于我们已将两列编入索引,因此我们仍然有一个矩阵,即使我们没有为索引操作指定X
(请参阅R: Extract or Replace Parts of an Object) 。这很方便,可以看出。
在R中,&#34;矩阵角色&#34;一个矩阵实际上只是一个普通的原子矢量,而且#34;矢量角色&#34;可以利用矩阵的矢量化操作。这是我们将X[,c('val','fill')];
## val fill
## [1,] 10 0.1
## [2,] 20 0.2
## [3,] 30 0.3
## [4,] 40 0.4
## [5,] 50 0.5
## [6,] 60 0.6
和drop=F
数据传递给val
并将这些元素重复进行适当重复的方式。
然而,在执行此操作时,重要的是要准确理解如何将矩阵视为向量。答案是,向量是通过跟随跨行的元素来形成的,然后才是跨列。 (对于高维数组,然后遵循后续维.IOW,向量的顺序是跨行,然后是列,然后是z切片等。)
如果仔细查看上面的矩阵,您会发现它不能用作fill
的{{1}}参数,因为rep()
s将首先被跟踪,然后是x
。我们实际上可以相当容易地构造一个rep()
参数来重复每个元素正确的次数,但是生成的向量将完全无序,并且没有办法将其重塑为所需的矩阵val
。
实际上,为什么我不能在继续解释之前快速证明这一点:
fill
虽然上面的向量在所有正确的重复中都有所有正确的元素,但是顺序是这样的,它不能形成所需的输出矩阵times
。
因此,我们可以通过首先转置提取物来解决这个问题:
Y
现在我们将rep(X[,c('val','fill')],times=c(X[,'rep'],YC-X[,'rep']))
## [1] 10.0 20.0 20.0 30.0 30.0 30.0 40.0 40.0 40.0 40.0 60.0 0.1 0.1 0.1 0.2 0.2 0.3 0.5 0.5 0.5 0.5 0.6 0.6 0.6
和Y
向量相互交错,这样,当展平为向量时,当我们将其作为参数传递给内部使用它的函数时会发生这种情况作为向量,例如我们将对t(X[,c('val','fill')]);
## [,1] [,2] [,3] [,4] [,5] [,6]
## val 10.0 20.0 30.0 40.0 50.0 60.0
## fill 0.1 0.2 0.3 0.4 0.5 0.6
val
参数进行处理,我们将获得fill
和相应的rep()
值命令重建矩阵。让我通过明确将矩阵展平为向量来展示这一点,以显示它的外观(正如您所看到的,这可以通过简单的x
调用来完成&#34;展平&#34;)
val
所以,我们有fill
个参数。现在我们只需要构造c()
参数。
这实际上相当棘手。首先,我们可以识别c(t(X[,c('val','fill')]));
## [1] 10.0 0.1 20.0 0.2 30.0 0.3 40.0 0.4 50.0 0.5 60.0 0.6
值的重复计数直接在x
的{{1}}列中提供,因此我们在times
中有。{1}}。并且val
值的重复计数可以根据我在rep
中捕获的输出矩阵X
中的列数与上述值之间的差异来计算重复计算为X[,'rep']
或IOW,fill
。问题是,我们需要将这两个向量交错以与Y
参数对齐。
我不知道任何&#34;内置&#34;在R中交织两个向量的方法;似乎没有任何功能可以做到这一点。在处理这个问题时,我想出了两个不同的可能解决方案来完成这个任务,其中一个在性能和简洁性方面似乎都更好。但是,因为我写了我的原始解决方案,使用&#34;更糟糕的&#34;一,而且只是稍后(实际上写这个解释时)想到了第二个和更好的&#34;一,我将在这里解释这两种方法,从第一个和更糟的方法开始。
交错解决方案#1
交错两个向量可以通过顺序组合向量,然后用精心设计的索引向量索引组合向量,该向量基本上从组合向量的前半部分到后半部分来回跳跃,顺序拉动以交替的方式排除每一半的每个元素。
为了构造这个索引向量,我从一个长度等于组合向量长度的一半的顺序向量开始,每个元素重复一次:
YC
接下来,我添加一个由val
组成的双元素向量和组合向量的一半长度:
YC-X[,'rep']
第二个加数循环通过第一个加数,实现了我们需要的交错:
x
因此我们可以将组合的重复矢量索引以获得我们的rep(1:nrow(X),each=2);
## [1] 1 1 2 2 3 3 4 4 5 5 6 6
参数:
0
Interleaving Solution#2
交错两个矢量也可以通过将两个矢量组合成矩阵然后再次平坦化它们以它们自然地交错的方式来实现。我认为最简单的方法是nrow(X)*0:1;
## [1] 0 6
将它们放在一起,然后立即用rep(1:nrow(X),each=2)+nrow(X)*0:1;
## [1] 1 7 2 8 3 9 4 10 5 11 6 12
展平它们:
times
基于一些粗略的性能测试,似乎解决方案#2更具性能,并且可以清楚地看到它更简洁。此外,可以非常轻松地将其他向量添加到c(X[,'rep'],YC-X[,'rep'])[rep(1:nrow(X),each=2)+nrow(X)*0:1];
## [1] 1 3 2 2 3 1 4 0 0 4 1 3
调用中,但是在解决方案#1(几个增量)方面会涉及更多内容。
性能测试(使用大型数据集):
rbind()
因此,完整的c()
调用会以正确的顺序为我们提供数据:
c(rbind(X[,'rep'],YC-X[,'rep']));
## [1] 1 3 2 2 3 1 4 0 0 4 1 3
最后一步是使用rbind()
从中构建矩阵,因为这样的数据最终是从il1 <- function() c(X[,'rep'],YC-X[,'rep'])[rep(1:nrow(X),each=2)+nrow(X)*0:1];
il2 <- function() c(rbind(X[,'rep'],YC-X[,'rep']));
identical(il1(),il2());
## [1] TRUE
system.time({ replicate(30,il1()); });
## user system elapsed
## 3.750 0.000 3.761
system.time({ replicate(30,il1()); });
## user system elapsed
## 3.810 0.000 3.815
system.time({ replicate(30,il2()); });
## user system elapsed
## 1.516 0.000 1.512
system.time({ replicate(30,il2()); });
## user system elapsed
## 1.500 0.000 1.503
返回的。我们还必须指定所需的行数,这与输入矩阵rep()
相同(或者,如果需要,我们可以指定列数rep(t(X[,c('val','fill')]),times=c(rbind(X[,'rep'],YC-X[,'rep'])));
## [1] 10.0 0.1 0.1 0.1 20.0 20.0 0.2 0.2 30.0 30.0 30.0 0.3 40.0 40.0 40.0 40.0 0.5 0.5 0.5 0.5 60.0 0.6 0.6 0.6
,甚至两者都有) :
byrow=T
我们已经完成了!