Решение
Это было не легко, но я нашел способ, чтобы выполнить эту задачу с помощью одного векторизованного вызова 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
(мелочь: I решил присвоить имена столбцов rep val fill
до X
, а не a b c
, как указано в вопросе, и я использовал эти имена столбцов в своем решении при индексировании X
(вместо использования числовых индексов) по той причине, что я обычно предпочитаю максимизировать удобочитаемость Rever возможно, но эта деталь незначительна по отношению к корректности и производительности решения.)
Performance
Это на самом деле имеет значительное преимущество в производительности по сравнению @ josilber, решения которого, потому что он использует apply()
, который внутренне перебирает строку матрицы (традиционно называемой «скрытой петлей» в R-talk), тогда как ядро моего решения представляет собой единый векторизованный вызов 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()
, поскольку он поддерживает векторизованные значения повторения. Другими словами, для данного векторного ввода x
он может принимать векторный ввод для times
, который определяет, сколько раз повторять каждый элемент x
. Таким образом, задача просто конструирует соответствующие аргументы x
и times
.
Таким образом, решение начинается с извлечения 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
Как вы можете видеть, поскольку мы проиндексированы две колонки, у нас еще есть матрицу, даже если мы Ждут» t укажите drop=F
операции индекса (см. R: Extract or Replace Parts of an Object). Это удобно, как будет видно.
В R под «матрицей персоной» матрицы на самом деле просто простой старый атомный вектор, а «векторная персонаж» матрицы можно использовать для векторизованных операций. Таким образом мы можем передать данные val
и fill
в rep()
и соответствующим образом повторить эти элементы.
Однако при этом важно точно понимать , как матрица рассматривается как вектор. Ответ заключается в том, что вектор формируется следующими элементами через строки и только после этого через столбцы. (Для более массивных массивов затем следуют последующие размеры. IOW, порядок вектора находится по строкам, затем столбцы, затем z-срезы и т. Д.)
Если вы внимательно посмотрите на приведенную выше матрицу, что он не может использоваться как наш аргумент 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
, мы получим 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
аргумент. Теперь нам просто нужно построить аргумент times
.
Это было довольно сложно определить. Сначала мы можем признать, что подсчет повторений для значений val
предоставляется непосредственно в столбце rep
X
, поэтому мы имеем это в X[,'rep']
. И подсчет повторений для значений fill
может быть вычислен из разницы между количеством столбцов в выходной матрице Y
, которую я захватил в YC
, и вышеупомянутое количество повторений для или IOW, YC-X[,'rep']
. Проблема в том, что нам нужно чередовать эти два вектора, чтобы согласовать наш аргумент x
.
Я не знаю ни одного «встроенного» способа чередования двух векторов в R; не существует какой-либо функции, которая это делает. При работе над этой проблемой я придумал два различных возможных решения этой задачи, одна из которых, по-видимому, лучше с точки зрения производительности и точности. Но поскольку я написал свое первоначальное решение, чтобы использовать «худший», и только позже (при написании этого объяснения на самом деле) подумал о втором и «лучшем», я объясню оба подхода здесь, начиная с первого и худшего один.
Чередование Решение # 1
перемежения два вектора может быть сделано путем объединения векторов последовательно, а затем индексации, в сочетании вектор с тщательно обработанной индексом вектора, который в основном переходит назад и вперед от первой половины во вторую половину объединенного вектора, последовательно вытягивая каждый элемент каждой половины чередующимся образом.
Чтобы построить этот индекс вектора, я начинаю с последовательным вектором длиной, равной половине длины объединенного вектора, с повторяется один раз каждый элемент:
rep(1:nrow(X),each=2);
## [1] 1 1 2 2 3 3 4 4 5 5 6 6
Далее я добавить к тому, что двух- элемент вектору, состоящий из 0
и половины длине комбинированного вектора:
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
аргумент:
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
Чередование двух векторов также может быть достигнуто путем объединения двух векторов в матрицы, а затем снова сглаживают их, таким образом, что они, естественно, чередуются. Я считаю, что самый простой способ сделать это, чтобы 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 (пару шагов).
Тестирование производительности (с использованием большого набора данных):
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
И все готово!
Я действительно впечатлен вашим ответом. Большое вам спасибо за все ваши замечательные и подробные объяснения, и это действительно очень полезно для меня. Вы и josilber оба действительно удивительные :) Большое спасибо! – Ted