выберите последнее наблюдение из продольных данных - PullRequest
11 голосов
/ 28 февраля 2012

У меня есть набор данных с несколькими оценками времени для каждого участника. Я хочу выбрать последнюю оценку для каждого участника. Мой набор данных выглядит так:

ID  week  outcome
1   2   14
1   4   28
1   6   42
4   2   14
4   6   46
4   9   64
4   9   71
4  12   85
9   2   14
9   4   28
9   6   51
9   9   66
9  12   84

Я хочу выбрать только последнее наблюдение / оценку для каждого участника, но у меня есть только количество недель в качестве показателя для каждого участника. Как это можно сделать в R (или Excel?)

спасибо заранее,

Niki

Ответы [ 6 ]

11 голосов
/ 28 февраля 2012

Вот один из подходов base-R:

do.call("rbind", 
        by(df, INDICES=df$ID, FUN=function(DF) DF[which.max(DF$week), ]))
  ID week outcome
1  1    6      42
4  4   12      85
9  9   12      84

Кроме того, пакет data.table предлагает краткий и выразительный язык для выполнения операций с фреймами данных этого типа:

library(data.table)
dt <- data.table(df, key="ID")

dt[, .SD[which.max(outcome), ], by=ID] 
#      ID week outcome
# [1,]  1    6      42
# [2,]  4   12      85
# [3,]  9   12      84

# Same but much faster. 
# (Actually, only the same as long as there are no ties for max(outcome)..)
dt[ dt[,outcome==max(outcome),by=ID][[2]] ]   # same, but much faster.

# If there are ties for max(outcome), the following will still produce
# the same results as the method using .SD, but will be faster
i1 <- dt[,which.max(outcome), by=ID][[2]]
i2 <- dt[,.N, by=ID][[2]]
dt[i1 + cumsum(i2) - i2,]

Наконец, вот решение на основе plyr

library(plyr)

ddply(df, .(ID), function(X) X[which.max(X$week), ])
#   ID week outcome
# 1  1    6      42
# 2  4   12      85
# 3  9   12      84
9 голосов
/ 04 марта 2012

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

dat[order(dat$ID,dat$Week),]  # Sort by ID and week
dat[!duplicated(dat$ID, fromLast=T),] # Keep last observation per ID

   ID Week Outcome
3   1    6      42
8   4   12      85
13  9   12      84
2 голосов
/ 30 апреля 2014

В этом ответе используется пакет data.table.Это должно быть очень быстро, даже с большими наборами данных.

setkey(DT, ID, week)              # Ensure it's sorted.
DT[DT[, .I[.N], by = ID][, V1]]

Объяснение: .I - целочисленный вектор, содержащий местоположения строк для группы (в данном случае группа - ID)..N - это целочисленный вектор длины один, содержащий количество строк в группе.Итак, что мы делаем здесь, это извлекаем местоположение последней строки для каждой группы, используя «внутренний» DT[.], используя тот факт, что данные отсортированы в соответствии с ID и week.Впоследствии мы используем это для подстановки «внешнего» * ​​1010 *.

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

DT <- 
  data.table(
    ID = c(rep(1, 3), rep(4, 5), rep(9, 5)),
    week = c(2,4,6, 2,6,9,9,12, 2,4,6,9,12), 
    outcome = c(14,28,42, 14,46,64,71,85, 14,28,51,66,84))
2 голосов
/ 01 марта 2012

Я могу играть в эту игру. Я провел несколько тестов различий между lapply , sapply и по . Мне кажется, что чем больше вы контролируете типы данных и чем более просты операции, тем быстрее они выполняются (например, lapply обычно быстрее, чем sapply, и выполняется as.numeric (lapply (...)) Быстрее тоже). Имея это в виду, это дало те же результаты, что и выше, и может быть быстрее, чем остальные.

df[cumsum(as.numeric(lapply(split(df$week, df$id), which.max))), ]

Пояснение: мы хотим, чтобы именно .max на неделе для каждого идентификатора. Это обрабатывает содержимое lapply . Нам нужен только вектор этих относительных точек, поэтому сделайте его числовым. Результатом является вектор (3, 5, 5). Нам нужно добавить позиции предыдущих максимумов. Это достигается с помощью cumsum .

Следует отметить, что это решение не является общим, когда я использую cumsum . Может потребоваться, чтобы перед выполнением мы отсортировали фрейм по id и неделе. Я надеюсь, вы понимаете, почему (и знаете, как использовать с (df, order (id, week)) в индексе строки для достижения этой цели). В любом случае, он все равно может потерпеть неудачу, если у нас нет уникального максимума, потому что which.max берет только первый. Таким образом, мое решение - это вопрос о подмене, но само собой разумеется. Мы пытаемся извлечь очень конкретную информацию для очень конкретного примера. Наши решения не могут быть общими (хотя методы важны для понимания в целом).

Я оставлю это на усмотрение, чтобы обновить его сравнения!

2 голосов
/ 28 февраля 2012

Другой вариант в базе: df[rev(rownames(df)),][match(unique(df$ID), rev(df$ID)), ]

1 голос
/ 01 марта 2012

Я пытался использовать split и tapply, чтобы поближе познакомиться с ними.Я знаю, что на этот вопрос уже был дан ответ, но я подумал, что добавлю еще одно решение, используя split (простите за уродство; я более чем открыт для обратной связи для улучшения; подумал, что, возможно, было какое-то применение для уменьшения кода):

sdf <-with(df, split(df, ID))
max.week <- sapply(seq_along(sdf), function(x) which.max(sdf[[x]][, 'week']))
data.frame(t(mapply(function(x, y) y[x, ], max.week, sdf)))

Я также подумал, почему у нас есть 7 ответов, что он был готов для эталона.Результаты могут вас удивить (использование rbenchmark с R2.14.1 на компьютере с Win 7):

# library(rbenchmark)
# benchmark(
#     DATA.TABLE= {dt <- data.table(df, key="ID")
#         dt[, .SD[which.max(outcome),], by=ID]},
#     DO.CALL={do.call("rbind", 
#         by(df, INDICES=df$ID, FUN=function(DF) DF[which.max(DF$week),]))},
#     PLYR=ddply(df, .(ID), function(X) X[which.max(X$week), ]),
#     SPLIT={sdf <-with(df, split(df, ID))
#         max.week <- sapply(seq_along(sdf), function(x) which.max(sdf[[x]][, 'week']))
#         data.frame(t(mapply(function(x, y) y[x, ], max.week, sdf)))},
#     MATCH.INDEX=df[rev(rownames(df)),][match(unique(df$ID), rev(df$ID)), ],
#     AGGREGATE=df[cumsum(aggregate(week ~ ID, df, which.max)$week), ],
#     #WHICH.MAX.INDEX=df[sapply(unique(df$ID), function(x) which.max(x==df$ID)), ],
#     BRYANS.INDEX = df[cumsum(as.numeric(lapply(split(df$week, df$ID), 
#         which.max))), ],
#     SPLIT2={sdf <-with(df, split(df, ID))
#         df[cumsum(sapply(seq_along(sdf), function(x) which.max(sdf[[x]][, 'week']))),
#         ]},
#     TAPPLY=df[tapply(seq_along(df$ID), df$ID, function(x){tail(x,1)}),],
# columns = c( "test", "replications", "elapsed", "relative", "user.self","sys.self"), 
# order = "test", replications = 1000, environment = parent.frame())

          test replications elapsed  relative user.self sys.self
6    AGGREGATE         1000    4.49  7.610169      2.84     0.05
7 BRYANS.INDEX         1000    0.59  1.000000      0.20     0.00
1   DATA.TABLE         1000   20.28 34.372881     11.98     0.00
2      DO.CALL         1000    4.67  7.915254      2.95     0.03
5  MATCH.INDEX         1000    1.07  1.813559      0.51     0.00
3         PLYR         1000   10.61 17.983051      5.07     0.00
4        SPLIT         1000    3.12  5.288136      1.81     0.00
8       SPLIT2         1000    1.56  2.644068      1.28     0.00
9       TAPPLY         1000    1.08  1.830508      0.88     0.00

Edit1: Я пропустил решение WHICH MAX, поскольку оно не возвращает правильные результатыи вернул AGGREGATE также решение, которое я хотел использовать (комплименты Брайана Гудрича) и обновленную версию split, SPLIT2, используя cumsum (мне понравилось это движение).

Edit 2: Дейсон также присоединился к решению, которое я бросил в тест, который тоже неплохо справился.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...