2015-05-11 1 views
1

У меня действительно большой набор данных, поэтому я пытаюсь обобщить свой вопрос с помощью небольшого примера ниже.Как создать матрицу с различными повторами значений в векторе

Допустим, у меня есть матрица 3X3 с именем X, с именами столбцов 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) дает значения для заполнения, если число раз в a меньше 4 (число столбцов матрицы Y).

Моя цель состоит в том, чтобы создать матрицу 3x4 Y, которая должна быть как этот

Y = (10, 0.1, 0.1, 0.1, 
    20, 20, 0.2, 0.2, 
    30, 30, 30, 0.3) 

Я понимаю, что там может быть много способов сделать этот пример, но так как мои реальные данные действительно большие (X имеет миллион строк, а Y имеет 480 столбцов), мне действительно нужно сделать это без циклов (например, 480 итераций). Я попытался использовать функцию rep, но все равно не мог этого сделать.

ответ

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 

(мелочь: 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 предоставляется непосредственно в столбце repX, поэтому мы имеем это в 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 

И все готово!

+0

Я действительно впечатлен вашим ответом. Большое вам спасибо за все ваши замечательные и подробные объяснения, и это действительно очень полезно для меня. Вы и josilber оба действительно удивительные :) Большое спасибо! – Ted

4

Каждая строка матрицы выходов может быть вычислена с помощью одного вызова функции rep, что делает всю операцию в 1-лайнер:

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 х 480 которая, как мы надеемся, будет вписываться в память вашей системы. Однако вы, вероятно, не сможете сделать это слишком большим, не исчерпывая системную память.

+0

@Ted 'apply (X, 1, f)' запускает функцию 'f' в каждой строке' X'. Функция, которую мы выбрали для запуска, - это функция (x) rep (x [2: 3], c (x [1], 4-x [1])) ', которая вызывает переданную строку' x', а затем передает различные части этой строки функции 'rep', возвращая результат. Вы можете найти [следующее] (http://www.r-bloggers.com/the-r-apply-function-a-tutorial-with-examples/) полезным в качестве учебника по функции 'apply'. – josliber

 Смежные вопросы

  • Нет связанных вопросов^_^