Замена циклов for для Excel-подобного заполнения формул в фрейме данных / матрице - PullRequest
0 голосов
/ 11 января 2019

Я пытаюсь выполнить базовое заполнение формул в стиле Excel в R. Я хочу заполнить значение «ячейка» на основе значений других ячеек в той же матрице или data.frame. Функция довольно проста для работы с одной ячейкой, но ее сложнее масштабировать как по строкам, так и по столбцам.

Скажите, у меня есть простая матрица:

simple <- matrix(c(0,1,2,3,0,4,5,6,7,NA,NA,NA,8,NA,NA,NA), nrow = 4, ncol = 4)

     [,1] [,2] [,3] [,4]
[1,]    0    0    7    8
[2,]    1    4   NA   NA
[3,]    2    5   NA   NA
[4,]    3    6   NA   NA

Я хочу заполнить NA с суммой столбцов 1 и 2 в той же строке и строки 1 в том же столбце. В Excel для ячейки C2 это будет

=$A2 + $B2 + C$1

в R

simple[2,3] <- simple[2,1] + simple[2,2] + simple[1,3]

В Excel вы можете просто перетащить формулу на оставшиеся ячейки и вуаля. В R не все так просто.

Так как r векторизована, я могу довольно легко заполнить весь столбец, задав диапазоны вместо отдельных ячеек, например:

simple[2:4,3] <- simple[2:4,1] + simple[2:4,2] + simple[1,3]

     [,1] [,2] [,3] [,4]
[1,]    0    0    7    8
[2,]    1    4   12   NA
[3,]    2    5   14   NA
[4,]    3    6   16   NA

Но когда я пытаюсь векторизовать как строки, так и столбцы, это не сработает, потому что оно интерпретирует последнее значение как вектор с (7,8) и пытается добавить это в виде строк, а не добавив его по столбцам.

simple[2:4,3:4] <- simple[2:4,1] + simple[2:4,2] + simple[1,3:4]

Warning message:
In simple[2:4, 1] + simple[2:4, 2] + simple[1, 3:4] :
  longer object length is not a multiple of shorter object length

     [,1] [,2] [,3] [,4]
[1,]    0    0    7    8
[2,]    1    4   12   12
[3,]    2    5   15   15
[4,]    3    6   16   16

В качестве альтернативного решения можно сделать вложенные для циклов, как показано ниже:

for (i in 2:4){
  for (j in 3:4){
    simple[i,j] <- simple[i,1] + simple[i,2] + simple[1,j]
  }
}

     [,1] [,2] [,3] [,4]
[1,]    0    0    7    8
[2,]    1    4   12   13
[3,]    2    5   14   15
[4,]    3    6   16   17

Это на самом деле работает и довольно просто, но включает в себя вложенные циклы, так что достаточно сказано.

Мне кажется, что "правильным" решением было бы решение, использующее правильную векторизацию, apply () или dplyr, но я не могу понять, как заставить их работать, за исключением перестановки данных из формата кросс-таблицы в плоский формат, но это может очень быстро увеличить размер файла.

Есть какие-нибудь идеи о том, как сделать это более модным?

Ответы [ 3 ]

0 голосов
/ 13 января 2019

Возможно, я опаздываю к игре, но вот решение data.table и base R, которое для больших наборов данных намного быстрее, чем tidyverse. Синтаксис на первый взгляд может показаться более запутанным, но разбивать его по частям очень логично и просто, если вы хорошо разбираетесь в lapply.

Для совместимости добавляемой ячейки и векторов необходимо преобразовать ячейку в вектор, просто реплицировав это значение столько раз, сколько число наблюдений или строк в кадре данных. Так что в вашем примере V3 = rep (7,4) даст вектор со всеми 7s. R тогда позволит вам сделать V3=V1+V2+V3, где V3 с правой стороны является повторением (7,4).

data.table имеет несколько удобных встроенных специальных символов только для чтения, которые также дадут вам возможность расширить решение за пределы двух столбцов, которые вы предоставили в примере. Два, которые я использую чаще всего, это .SD и .N. В этом примере вы можете думать о .SD как о способе обращения ко всем столбцам, кроме первых двух, и .N всегда является постоянным числом, равным количеству строк в data.table. Эти символы могут использоваться в слоте j таблицы data.table, которая эквивалентна столбцам матрицы или объекта data.frame. Итак, ваш код будет выглядеть так:

    simple <- data.table(simple)

    NAcols <- colnames(simple)[-c(1,2)] ##Can modify this to get names of columns you wish to change if its not the first two using match or grep. I can add that if you want?

    simple[,NAcols:=lapply(.SD,function(i) V1+V2+rep(i[1],.N)),.SDcols=NAcols]

Обратите внимание, что каждая итерация в цикле lapply представляет собой просто i-й столбец, и i [1] выбирает только первый элемент этого столбца и реплицирует его столько раз, сколько строк (.N), прежде чем сложить три вектора вместе , .SDcols используется для предотвращения применения этой функции к первым двум столбцам. Хотя в этой задаче не было необходимости группировать, data.table также позволяет вам указать «by =» в качестве аргумента, если вы хотите сгруппировать по определенному столбцу или столбцам в data.table перед применением функции. Наконец, обратите внимание, что мне не нужно было присваивать последнюю строку кода другому объекту R, потому что data.table обновляет старые столбцы «simple» с помощью указателей, поэтому он намного быстрее, чем базовый R и объекты фрейма данных Tidyverse. Однако вы можете использовать функцию копирования data.table следующим образом, если по какой-то причине вы хотите сохранить исходный файл data.table:

    final_result <- copy(simple)[,NAcols:=lapply(.SD,function(i) V1+V2+rep(i[1],.N)),.SDcols=NAcols]

В любом случае, я надеюсь, что это объяснение поможет, и если вам нужно, чтобы я что-то прояснил, пожалуйста, дайте мне знать! Удачи!

0 голосов
/ 13 января 2019

В матричной арифметике каждый компонент должен иметь одинаковое измерение или любой компонент, состоящий из одного элемента. Поэтому рассмотрите возможность выравнивания, реплицируя 7 и 8 для каждой необходимой строки 2-4 (то есть 3 раза). Затем транспонируйте для 2 X 3 размерности:

simple[2:4,3:4] <- simple[2:4,1] + simple[2:4,2] + t(replicate(length(2:4), simple[1,3:4]))

В качестве альтернативы рассмотрим sapply итерацию значений 7 и 8 соответственно:

simple[2:4,3:4] <- sapply(3:4, function(i) simple[2:4,1] + simple[2:4,2] + simple[1,i])

Чуть более сжато с rowSums и не указывать индексирование строк:

simple[,3:4] <- sapply(3:4, function(i) rowSums(simple[,1:2]) + simple[1,i])
0 голосов
/ 11 января 2019

Вот более R-подобный способ, давайте сначала конвертируем simple в data.frame.

library(tidyverse)

df1 <- as.data.frame(simple)

df1 %>% mutate(V3 = V1 + V2 + first(V3), V4 = V1 + V2 + first(V4))

  V1 V2 V3 V4
1  0  0  7  8
2  1  4 12 13
3  2  5 14 15
4  3  6 16 17

first из dplyr удобно, потому что позволяет вам привязаться к первому значению в столбце, как в Excel с C$1

...