Эффективный метод фильтрации и добавления на основе определенных условий (в данном случае 3 условий)

У меня есть кадр данных, который выглядит так

     a    b    c   d
     1    1    1   0
     1    1    1   200
     1    1    1   300
     1    1    2   0
     1    1    2   600
     1    2    3   0
     1    2    3   100
     1    2    3   200
     1    3    1   0

У меня есть кадр данных, который выглядит так

     a    b    c   d
     1    1    1   250
     1    1    2   600
     1    2    3   150
     1    3    1   0

я сейчас этим занимаюсь {

  n=nrow(subset(Wallmart, a==i &    b==j & c==k  ))
  sum=subset(Wallmart, a==i &    b==j & c==k  )
  #sum
  sum1=append(sum1,sum(sum$d)/(n-1))

}

Я хотел бы добавить столбец «d» и взять среднее значение, подсчитав количество строк без подсчета 0. Например, первая строка (200 + 300)/2 = 250. В настоящее время я создаю список, в котором хранится « d', но в идеале я хочу, чтобы он был в формате выше. Например, первая строка будет выглядеть так

     a    b    c   d
     1    1    1   250

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


person user2575429    schedule 27.04.2014    source источник
comment
... и чего именно вы пытаетесь достичь?   -  person gagolews    schedule 27.04.2014
comment
Я не вижу петли. Кажется, в вашем вопросе чего-то не хватает. В любом случае, никогда не используйте append в цикле.   -  person Roland    schedule 27.04.2014
comment
Извините, я отредактировал вопрос, теперь его должно быть легко понять. Спасибо.   -  person user2575429    schedule 27.04.2014
comment
@ user2575429, я обновил свой ответ после вашего редактирования.   -  person Henrik    schedule 27.04.2014


Ответы (3)


Вы можете попробовать aggregate:

aggregate(d ~ a + b + c, data = df, sum)
#   a b c   d
# 1 1 1 1 500
# 2 1 3 1   0
# 3 1 1 2 600
# 4 1 2 3 300

Как отметил @Roland, для больших наборов данных вы можете вместо этого попробовать data.table или dplyr, например:

library(dplyr)
df %>%
  group_by(a, b, c) %>%
  summarise(
    sum_d = sum(d))

# Source: local data frame [4 x 4]
# Groups: a, b
# 
#   a b c sum_d
# 1 1 1 1   500
# 2 1 1 2   600
# 3 1 2 3   300
# 4 1 3 1     0

Изменить после обновленного вопроса. Если вы хотите рассчитать среднее значение по группе, исключая нулевые строки, вы можете попробовать следующее:

aggregate(d ~ a + b + c, data = df, function(x) mean(x[x > 0]))
#   a b c   d
# 1 1 1 1 250
# 2 1 3 1 NaN
# 3 1 1 2 600
# 4 1 2 3 150

df %>%
  filter(d != 0) %>%
  group_by(a, b, c) %>%
  summarise(
    mean_d = mean(d))

#   a b c mean_d
# 1 1 1 1    250
# 2 1 1 2    600
# 3 1 2 3    150

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

df$d[df$d == 0] <- NA
df %>%
  group_by(a, b, c) %>%
  summarise(
    mean_d = mean(d, na.rm = TRUE))

#   a b c mean_d
# 1 1 1 1    250
# 2 1 1 2    600
# 3 1 2 3    150
# 4 1 3 1    NaN
person Henrik    schedule 27.04.2014
comment
+1 Но для миллиона наблюдений data.table или dplyr может быть предпочтительнее. - person Roland; 27.04.2014
comment
@Roland, спасибо за ваш комментарий! Я добавил альтернативу dplyr. - person Henrik; 27.04.2014
comment
Спасибо @Henrik, особенно за ответ после редактирования. - person user2575429; 27.04.2014

Это data.table решение для вашего последнего редактирования.

library(data.table)
DT <- setDT(df)[, if(any(d[d > 0])) mean(d[d > 0]) else 0, by = c("a","b","c")]
# a b c  V1
# 1: 1 1 1 250
# 2: 1 1 2 600
# 3: 1 2 3 150
# 4: 1 3 1   0

Редактировать № 2:

Предложение @Arun ускорить его

setDT(df)[, mean(d[d > 0]), by = c("a","b","c")][is.nan(V1), V1 := 0]

Редактировать №3

@eddis предложение

setDT(df)[, sum(d) / pmax(1, sum(d > 0)), by = list(a, b, c)]
person David Arenburg    schedule 27.04.2014
comment
Спасибо, Дэвид, за предложение альтернативного метода. NaN не проблема, я это исправлю. - person user2575429; 27.04.2014
comment
это немного быстрее: setDT(df)[, sum(d) / pmax(1, sum(d > 0)), by = list(a, b, c)] - person eddi; 28.04.2014

Вот еще один способ:

Шаг 1: Настройка таблицы данных:

df <- read.table(text="     a    b    c   d
     1    1    1   0
     1    1    1   200
     1    1    1   300
     1    1    2   0
     1    1    2   600
     1    2    3   0
     1    2    3   100
     1    2    3   200
     1    3    1   0",header=T)
library(data.table)
setDT(df)
setkey(df,a,b,c)

Шаг 2: Выполните вычисления:

df[,sum(d)/ifelse((cnt=length(which(d>0)))>0,cnt,1),by=key(df)]

Обратите внимание, что зацикливание здесь не рекомендуется. И лучшая стратегия — векторизовать решение, как в примере выше.

Шаг 3: Давайте проверим время:

> dt<-df
> for(i in 1:20) dt <- rbind(dt,dt)
> dim(dt)
[1] 9437184       4
> setkey(dt,a,b,c)
> dt[,sum(d)/ifelse((cnt=length(which(d>0)))>0,cnt,1),by=key(dt)]
   a b c  V1
1: 1 1 1 250
2: 1 1 2 600
3: 1 2 3 150
4: 1 3 1   0
> system.time(dt[,sum(d)/ifelse((cnt=length(which(d>0)))>0,cnt,1),by=key(dt)])
   user  system elapsed 
  0.495   0.090   0.609 

Таким образом, вычисление для почти 10 миллионов записей выполняется примерно за 0,5 секунды!

Надеюсь это поможет!!

person Shambho    schedule 28.04.2014
comment
два комментария - несправедливо установить ключ, а затем оставить это вне вашего тайминга (не такая уж большая проблема, так как установка ключа не слишком сильно меняет скорость, но все же), и см. мой комментарий в другом data.table ответьте на более простой способ сделать то, что вы сделали - person eddi; 29.04.2014
comment
Спасибо @эдди. По первому пункту: я пытался проиллюстрировать скорость выполнения, а так как установка ключа не занимала много времени, то я его не включал. Однако я заметил здесь важную вещь: 20-кратный rbind на dt работает намного быстрее по сравнению с 20-кратным rbind на df. Есть комментарии по этому поводу?? Второй пункт очень хорошо принят и действительно оценен! - person Shambho; 29.04.2014
comment
Не знаю, что комментировать, кроме того, что data.table rbind просто лучше :) Он использует rbindlist внутри, что очень быстро. - person eddi; 29.04.2014