Применение агрегатной функции для нескольких разных срезов - PullRequest
4 голосов
/ 15 февраля 2011

У меня есть массив данных, который содержит некоторую информацию о людях и проектах как таковых:

person_id | project_id | action | time
--------------------------------------
        1 |          1 |      w |    1
        1 |          2 |      w |    2
        1 |          3 |      w |    2
        1 |          3 |      r |    3
        1 |          3 |      w |    4
        1 |          4 |      w |    4
        2 |          2 |      r |    2
        2 |          2 |      w |    3

Я хотел бы дополнить эти данные парой дополнительных полей с именами «first_time» и «first_time_project», которые в совокупности определяют первый раз, когда любое действие этого человека было замечено, и первый раз, когда разработчик увидел какое-либо действие в проекте. В итоге данные должны выглядеть так:

person_id | project_id | action | time | first_time | first_time_project
------------------------------------------------------------------------
        1 |          1 |      w |    1 |          1 |                  1
        1 |          2 |      w |    2 |          1 |                  2
        1 |          3 |      w |    2 |          1 |                  2
        1 |          3 |      r |    3 |          1 |                  2
        1 |          3 |      w |    4 |          1 |                  2
        1 |          4 |      w |    4 |          1 |                  4
        2 |          2 |      r |    2 |          2 |                  2
        2 |          2 |      w |    3 |          2 |                  2

Мой наивный способ написать пару циклов:

for (pid in unique(data$person_id)) {
    data[data$pid==pid, "first_time"] = min(data[data$pid==pid, "time"])
    for (projid in unique(data[data$pid==pid, "project_id"])) {
        data[data$pid==pid & data$project_id==projid, "first_time_project"] = min(data[data$pid==pid & data$project_id==projid, "time"]
    }
}

Теперь, не нужно быть гением, чтобы увидеть, что это будет медленно, с двойными вложенными циклами. Тем не менее, я не могу найти способ справиться с этим в R. Я вроде эмулирую группу опцией для SQL. Я знаю, что это может помочь, но я не могу понять, как сделать несколько слайсов.

Есть какие-нибудь подсказки о том, как перевести мой код с ледяной скорости на что-то более быстрое? Я был бы счастлив с улиткой прямо сейчас.

Ответы [ 5 ]

4 голосов
/ 15 февраля 2011

Попробуйте ave:

transform(data, 
   first_time = ave(time, person_id, FUN = min),
   first_time_project = ave(time, person_id, project_id, drop = TRUE, FUN = min)
)
4 голосов
/ 15 февраля 2011

Сочетание plyr и transform () Хэдли является мощным. Если я правильно понимаю ваш вопрос, то:

foo <- ddply(foo, .(person_id), transform, first_time=min(time))
foo <- ddply(foo, .(person_id, project_id), transform, 
  first_time_project=min(time))
3 голосов
/ 11 сентября 2012

Если скорость - это то, что вы ищете, тогда data.table - путь.

library(data.table)
DT <- data.table(foo)
DT[, first_time := min(time), by = person_id]
DT[, first_time_project := min(time), by = list(person_id, project_id)]
1 голос
/ 15 февраля 2011

Быстрый и грязный раствор без петель

library(plyr)


# function to get first time by any person/project
fp <- function(dat) 
{
dat$first_time=min(dat$time)
ftp <- function(d) { d$first_time_project=min(d$time); return (d) }
dat=ddply(dat, .(project_id), ftp)
return (dat)
}


#this single call should give you the result you want
result=ddply(data, .(person_id), fp) 
0 голосов
/ 15 февраля 2011

Быстрый способ, которым я могу придумать:

foo <- data.frame(
       person_id=rep(1:5,each=6),
       project_id=sample(1:5,30,T),
       time=sample(1:30))

first_time <- aggregate(foo$time, list(foo$person_id), min)

foo$first_time <- first_time[ match(foo$person_id,first_time[,1]),2]

bar <- subset(foo, time==first_time)

foo$first_time_project <- bar$project_id[match(foo$person_id, bar$person_id)]
...