Как использовать цикл for для применения функции с несколькими аргументами к фрейму данных на основе переменной группировки? - PullRequest
0 голосов
/ 12 февраля 2019

Преамбула

Я заранее извиняюсь, мне очень трудно выразить в письменной форме точную проблему и думаю, что она наиболее понятна, глядя на код.Кроме того, я относительно новичок в R и испытываю затруднения с использованием правильных слов для точного описания ситуации.Я думаю, что решение должно быть легко указать кем-то с немного большим опытом, любой совет будет высоко ценится!

Описание

У меня есть специальный расчет, который я пытаюсь сделать нагрупп за базой, для которой я написал функцию.Функция определяется пользователем для выполнения этого специализированного вычисления, требует 4 аргумента (2 из которых имеют длину> 1) и выводит одно значение (поэтому выходные данные не равны длине ввода).Хотя эта функция работает, мне нужно иметь возможность эффективно применять ее к каждой группе в фрейме данных (для воспроизводимого примера ниже есть 4 группы, но на самом деле это будет 100 или 1000 групп).

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

Несмотря на это, казалось, что цикл for будет простым способом применить мою функцию к строкам, связанным с каждой группой.Однако я не могу генерировать желаемый результат.Как я упоминал в преамбуле, я думаю, что это просто потому, что я упускаю из виду / не знаю что-то очень фундаментальное, например, необходимость делать цикл внутри цикла или индексировать цикл for по-другому.

Воспроизводимый пример

Функционально аналогичные данные

interval=0.05 #used here to generate v1 and again in the function
v1 = seq(0.00000000001,1.00000000001, by=interval) 
nrows = length(v1) #determines length of other variables
g1 = c(rep(23.4, nrows), rep(19.7, nrows),rep(25.2, nrows),rep(16.4, 
nrows))           
v2 = runif(length(g1), 0,1)
dat = as.data.frame(cbind(g1,v1,v2))

Где:

  • g1 - переменная группировки
  • v1 - первый аргумент, повторяется для каждой группировкиvar
  • v2 - второй аргумент, представляющий вероятность, связанную с каждым v1
  • , dat - фрейм данных

Функция

(Этомоя первая функция, и я предполагаю, что есть лучший способ написать ее, но она работает)

MyFunction = function(v1, v2, interval, nrows) {
  sum.prod = sum(v1[2:nrows-1] * v2[2:nrows-1])
  last.val = v2[nrows]/2
  out = 2 * (sum.prod+last.val) * interval
  out
  }

Доказательство того, что функция работает

Я предоставляю вычисление для первой переменной группировки(g1 = 23.4) на всякий случай полезно подтвердить, что функция работает и как она работает, поскольку для этой функции нет документации

range1 = 1:nrows
g1.sub1 = dat$g1[range1]
v1.sub1 = dat$v1[range1]
v2.sub1 = dat$v2[range1]

g.first = 2 * ((v1.sub1[2] * v2.sub1[2])+
(v1.sub1[3] * v2.sub1[3]) + (v1.sub1[4] * v2.sub1[4]) +
(v1.sub1[5] * v2.sub1[5]) + (v1.sub1[6] * v2.sub1[6]) +
(v1.sub1[7] * v2.sub1[7]) + (v1.sub1[8] * v2.sub1[8]) +
(v1.sub1[9] * v2.sub1[9]) + (v1.sub1[10] * v2.sub1[10]) +
(v1.sub1[11] * v2.sub1[11]) + (v1.sub1[12] * v2.sub1[12]) +
(v1.sub1[13] * v2.sub1[13]) + (v1.sub1[14] * v2.sub1[14]) +
(v1.sub1[15] * v2.sub1[15]) + (v1.sub1[16] * v2.sub1[16]) +
(v1.sub1[17] * v2.sub1[17]) + (v1.sub1[18] * v2.sub1[18]) +
(v1.sub1[19] * v2.sub1[19]) + (v1.sub1[20] * v2.sub1[20]) +
v2.sub1[21] / 2) * interval

g.first

Что соответствует значению, данному:

MyFunction(v1 = v1.sub1, v2 = v2.sub1, interval = interval, nrows=nrows)

Где я застрял: цикл For *

Как я упоминал в описании, я пробовал различные подходы для решения этой проблемы, включая применение семейства функций без удачи.Следующий код представляет самое близкое, что я пришел.Однако это только дает мне правильное значение для первого элемента в g1 (23.4) четыре раза, а не правильное значение для каждого из четырех элементов в g1 (23.4, 19.9.25.2,16.4) один раз.

g=c(unique((g1)))
out=NULL
for(i in seq_along(g)){
out[i]=MyFunction( v1 = v1, v2 = v2, interval = interval, nrows = 
nrows)
}
out

Попытка устранить неполадки цикла For

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

g=c(unique((g1)))

range1 = 1:nrows
range2 = (nrows+1):(nrows*2)
range3 = (nrows*2+1):(nrows*3)
range4 = (nrows*3+1):(nrows*4)

out1=NULL
out2=NULL
out3=NULL
out4=NULL

for(i in seq_along(g)){
out1[i]=MyFunction( v1 = dat$v1[range1], v2 = dat$v2[range1], 
interval = interval, nrows = nrows)
out2[i]=MyFunction( v1 = dat$v1[range2], v2 = dat$v2[range2], 
interval = interval, nrows = nrows)
out3[i]=MyFunction( v1 = dat$v1[range3], v2 = dat$v2[range3], 
interval = interval, nrows = nrows)
out4[i]=MyFunction( v1 = dat$v1[range4], v2 = dat$v2[range4], 
interval = interval, nrows = nrows)
}

out1
out2
out3
out4

Желаемый результат

В идеале, окончательный вариантвывод будет представлять собой таблицу / матрицу / список / фрейм данных, который содержит каждое значение g1 и соответствующее значение, выводимое функцией "out"

Что-то вроде:

g1      out
23.4    some value between 0 and 1
19.9    some value between 0 and 1
25.2    some value between 0 and 1
16.4    some value between 0 and 1

Заключительные мысли

Поскольку моя «Попытка устранить неполадки цикла For» в конечном итоге смогла обеспечить правильные результаты, хотя и нежелательным образом (трудоемкий, не масштабируемый, и он выдает 4 идентичных значения для каждой группы, а не 1 значение для каждой группы)), Я думаю, это указывает на то, что в моем коде отсутствует что-то фундаментальное (например, другой цикл, другая переменная для seq_along, неправильное индексирование и т. Д.).Я надеюсь, что для более опытного пользователя это легко идентифицировать и объяснить, поскольку я в тупике.

Заранее спасибо!

Ответы [ 2 ]

0 голосов
/ 12 февраля 2019

Я понимаю, что вы просили цикл for, но, как вы, наверное, видели раньше, обычно есть лучший способ сделать это.Я думаю, вы еще не знакомы с пакетом data.table, думайте о нем как о надбавленном data.frame.

Итак, вы хотите применить MyFunction к вашим данным, сгруппированным постолбец g1.Это может быть легко достигнуто в data.table следующим образом.

library(data.table)
DT <- as.data.table(dat)
DT[, .(out = MyFunction(v1, v2, interval, .N)), by = g1]

Итак, эти строки сначала загружают библиотеку (вам, возможно, придется сначала установить ее с install.packages('data.table'). Затем преобразовать data.frame до data.table. Наконец, вычислите столбец out как MyFunction, примененный к v1, v2, interval and .N (представьте .N как nrows), сгруппированный по g1.

Я думаю, что это достигаетсяВаша цель, если у вас есть какие-либо вопросы, не стесняйтесь спрашивать. Надеюсь, это поможет.

0 голосов
/ 12 февраля 2019

Вот подход с использованием тидиверса.

Сначала давайте рассмотрим пример, заменив MyFunction несколькими строками, которые фиксируют описанный вами процесс суммирования:

library(tidyverse)
dat %>%
  slice(1:21) %>%  # Just the first grouping variable
  slice(2:n()) %>% # Exclude first row; has small impact since v1[1] is nearly zero already...
  mutate(prod = if_else(row_number() < n(),  # For all rows but the last one in the group,
                        v1 * v2,             # ... get the product of v1 and v2
                        v2/2)) %>%           # ... or have of v2, for the last row
  summarize(out = 2 * sum(prod) * interval)  # Sum the "prod" row, * 2 * interval

#        out
#1 0.5980449

Для этогодля всех групп g1 мы сначала добавим group_by, а затем выполните те же шаги суммирования отдельно для каждой группы:

dat %>%
  group_by(g1) %>%
  slice(1:21) %>%  # Just the first grouping variable
  slice(2:n()) %>% # Exclude first row; has small impact since v1[1] is nearly zero already...
  mutate(prod = if_else(row_number() < n(),  # For all rows but the last one in the group,
                        v1 * v2,             # ... get the product of v1 and v2
                        v2/2)) %>%           # ... or have of v2, for the last row
  summarize(out = 2 * sum(prod) * interval)  # Sum the "prod" row, * 2 * interval

## A tibble: 4 x 2
#     g1   out
#  <dbl> <dbl>
#1  16.4 0.342
#2  19.7 0.514
#3  23.4 0.598
#4  25.2 0.568
...