解決
簡単ではありませんでしたが、単一のベクトル化された への呼び出し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をに割り当てることを選択し、インデックス作成時に(数値インデックスを使用するのではなく)それらの列名をソリューションで使用しました。ただし、ソリューションの正確性とパフォーマンスに関しては、この詳細は無視できます。)Xa b cX
パフォーマンス
@josilber のソリューションよりもパフォーマンスが大幅に向上しますapply()。これは、マトリックスの行を内部的にループする (伝統的に R スピークでは「隠しループ」と呼ばれる) を使用しているためです。一方、私のソリューションのコアは、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
そして、@josilber と私がこの大規模な入力に対してもまったく同じ結果を得ていることを証明するために:
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()反復ステップは、ベクトル化された反復カウントをサポートしているため、 の 1 回の呼び出しを使用して実行できます。つまり、指定されたベクトル入力に対して、 の各要素を何回繰り返すかを指定xするベクトル入力を受け取ることができます。したがって、課題は、適切な引数と引数を構築することになります。timesxxtimes
したがって、解決策は、 の列valとfill列を抽出することから始まりXます。
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
ご覧のとおり、2 つの列にインデックスを付けたのでdrop=F、インデックス操作を指定していなくても、まだマトリックスがあります (「R: オブジェクトの一部を抽出または置換する」を参照)。これは便利です。
R では、マトリックスの「マトリックス ペルソナ」の下にあるのは、実際には単純な古いアトミック ベクトルであり、マトリックスの「ベクトル ペルソナ」はベクトル化された操作に活用できます。これは、valとfillデータを に渡し、rep()それらの要素を適切に繰り返す方法です。
ただし、これを行う場合、行列がベクトルとしてどのように扱われるかを正確に理解することが重要です。答えは、ベクトルは行を横切って要素をたどってから列を横切ることによって形成されるということです。(高次元配列の場合、後続の次元が続きます。IOW、ベクトルの順序は、行、列、z スライスなどです)
上記の行列を注意深く見ると、最初に s が続き、次に s が続くため、xへの引数として使用できないことがわかります。実際には、各要素を正しい回数繰り返す引数をかなり簡単に作成できますが、結果のベクトルは完全に順不同になり、目的の行列に再形成する方法はありません。rep()valfilltimesY
実際、説明に進む前に、これを簡単に示してみませんか。
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()xvalfillc()
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議論があります。timesあとは、引数を作成するだけです。
これを理解するのは実際にはかなりトリッキーでした。まず、値の繰り返し回数がの列にval直接提供されていることがわかります。また、値の繰り返し回数は、で取得した出力行列 の列数と、 、または IOWの前述の繰り返し回数との差から計算できます。問題は、これらの 2 つのベクトルをインターリーブして、議論に合わせる必要があることです。repXX[,'rep']fillYYCvalYC-X[,'rep']x
Rで2つのベクトルをインターリーブする「組み込み」の方法を知りません。それを行う機能はないようです。この問題に取り組んでいるときに、このタスクに対して 2 つの異なる解決策を思いつきました。そのうちの 1 つが、パフォーマンスと簡潔さの両方の点で優れているようです。しかし、私は最初の解決策を「より悪い」ものを使用するように書き、後で (実際にはこの説明を書いているときに) 2 番目の「より良い」ものを考えたので、最初と悪いものから始めて、ここで両方のアプローチを説明します。 1。
インターリーブ ソリューション #1
2 つのベクトルをインターリーブするには、ベクトルを順番に組み合わせてから、その組み合わせたベクトルを慎重に作成されたインデックス ベクトルでインデックス付けします。このインデックス ベクトルは基本的に、組み合わせたベクトルの前半から後半に前後にジャンプし、各要素を順番に引き出します。交互に半分ずつ。
このインデックス ベクトルを構築するために、結合されたベクトルの長さの半分に等しい長さの順次ベクトルから始めます。各要素は 1 回繰り返されます。
rep(1:nrow(X),each=2);
## [1] 1 1 2 2 3 3 4 4 5 5 6 6
0次に、結合されたベクトルの長さの半分で構成される 2 要素ベクトルを追加します。
nrow(X)*0:1;
## [1] 0 6
2 番目の加数は最初の加数を循環し、必要なインターリーブを実現します。
rep(1:nrow(X),each=2)+nrow(X)*0:1;
## [1] 1 7 2 8 3 9 4 10 5 11 6 12
したがって、結合された繰り返しベクトルにインデックスを付けて、times引数を取得できます。
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
インターリーブ ソリューション #2
2 つのベクトルをインターリーブすることは、2 つのベクトルを組み合わせて行列にし、それらが自然にインターリーブされるようにもう一度平坦化することによっても実現できます。これを行う最も簡単な方法は、rbind()それらを一緒にしてから、次のようにすぐに平らにすることだと思いc()ます。
c(rbind(X[,'rep'],YC-X[,'rep']));
## [1] 1 3 2 2 3 1 4 0 0 4 1 3
いくつかの大まかなパフォーマンス テストに基づくと、解決策 2 の方がパフォーマンスが高く、より簡潔であることが明らかにわかります。また、追加のベクトルを呼び出しに非常に簡単に追加できますがrbind()、解決策 1 に追加するには少し手間がかかります (2 回の増分)。
パフォーマンス テスト (大規模なデータセットを使用):
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は、データが から返される方法であるためですrep()。また、必要な行数も指定する必要があります。これは、入力行列と同じです(または、必要に応じて、列数、または両方をXR指定することもできます)。YC
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
これで完了です。