Выбрать последнее наблюдение из продольных данных

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

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?)

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

ники

Ответ 1

Вот один подход 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

Ответ 2

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

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

Ответ 3

Еще одна опция в базе: df[rev(rownames(df)),][match(unique(df$ID), rev(df$ID)), ]

Ответ 4

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

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

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

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

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

Ответ 5

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

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

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

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

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))

Ответ 6

Я пытаюсь использовать 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 (мне понравился этот ход).

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