从纵向数据中选择最后一个观察

时间:2012-02-27 20:23:21

标签: r

我有一个数据集,每个参与者都有几个时间评估。我想为每个参与者选择最后一次评估。我的数据集如下所示:

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

中做到这一点

提前感谢,

尼基

6 个答案:

答案 0 :(得分:11)

这是一种基础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

答案 1 :(得分:9)

如果您只是在寻找每人ID的最后一次观察,那么应该使用简单的两行代码。在可能的情况下,我总是寻求简单的基础解决方案,而拥有多种方法解决问题总是很棒。

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 :(得分:2)

基地的另一个选项:df[rev(rownames(df)),][match(unique(df$ID), rev(df$ID)), ]

答案 3 :(得分:2)

我可以玩这个游戏。我在 lapply sapply 之间的差异上运行了一些基准测试。在我看来,你对数据类型的控制越多,操作越基本,它就越快(例如,lapply通常比sapply更快,而as.numeric(lapply(...))正在进行更快,也)。考虑到这一点,这产生了与上述相同的结果,可能比其他结果更快。

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

说明:我们只想在每个id的周上使用which.max。它处理 lapply 的内容。我们只需要这些相对点的向量,所以将它设为数字。结果是向量(3,5,5)。我们需要添加先前最大值的位置。这是通过 cumsum 完成的。

应该注意,当我使用 cumsum 时,这个解决方案并不常用。它可能要求在执行之前我们在id和week上对帧进行排序。我希望你理解为什么(并且知道如何在行索引中使用 with(df,order(id,week))来实现这一点。在任何情况下,如果我们没有唯一的最大值,它可能仍然会失败,因为which.max只接受第一个。因此,我的解决方案有点问题,但不言而喻。我们试图为一个非常具体的例子提取非常具体的信息。我们的解决方案不可能是一般性的(尽管这些方法通常很重要)。

我会留下来更新他的比较!

答案 4 :(得分:2)

这个答案使用data.table包。即使数据集较大,它也应该非常快。

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

说明:.I是一个整数向量,包含组的行位置(在本例中为组ID)。 .N是一个长度为一的整数向量,包含组中的行数。所以我们在这里做的是使用“内部”DT[.]提取每个组的最后一行的位置,使用数据根据ID和{{1 }}。之后我们使用它来“外部”week

为了进行比较(因为它未在其他地方发布),以下是如何生成原始数据以便您可以运行代码:

DT[.]

答案 5 :(得分:1)

我一直在尝试使用split和tapply,以便更熟悉它们。我知道这个问题已经得到了回答,但我想我会用拆分添加另一个solotuion(原谅丑陋;我不仅仅对改进提出反馈意见;想想也许有一个用来减轻代码的用法):< / p>

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个答案它已经成熟了基准。结果可能会让您感到惊讶(在Win 7机器上使用rbenchmark和R2.14.1):

# 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

编辑1:我省略了WHICH MAX解决方案,因为它没有返回正确的结果并返回了我想要使用的AGGREGATE解决方案(Bryan Goodrich的赞美)和更新版本的拆分,SPLIT2,使用cumsum(我喜欢那个动作)。

编辑2: Dason还加入了一个tapply解决方案,我投入了相当不错的测试。