R子集唯一观测,保留最后一个条目。

6

我有一个数据框,看起来像这样(还有很多观察结果)

df <- structure(list(session_user_id = c("1803f6c3625c397afb4619804861f75268dfc567", 
"1924cb2ebdf29f052187b9a2d21673e4d314199b", "1924cb2ebdf29f052187b9a2d21673e4d314199b", 
"1924cb2ebdf29f052187b9a2d21673e4d314199b", "1924cb2ebdf29f052187b9a2d21673e4d314199b", 
"198b83b365fef0ed637576fe1bde786fc09817b2", "19fd8069c094fb0697508cc9646513596bea30c4", 
"19fd8069c094fb0697508cc9646513596bea30c4", "19fd8069c094fb0697508cc9646513596bea30c4", 
"19fd8069c094fb0697508cc9646513596bea30c4", "1a3d33c9cbb2aa41515e6ef76f123b2ea8ee2f13", 
"1b64c142b1540c43e3f813ccec09cb2dd7907c14", "1b7346d13f714c97725ba2e1c21b600535164291"
), raw_score = c(1, 1, 1, 1, 1, 0.2, NA, 1, 1, 1, 1, 0.2, 1), 
    submission_time = c(1389707078L, 1389694184L, 1389694188L, 
    1389694189L, 1389694194L, 1390115495L, 1389696939L, 1389696971L, 
    1389741306L, 1389985033L, 1389983862L, 1389854836L, 1389692240L
    )), .Names = c("session_user_id", "raw_score", "submission_time"
), row.names = 28:40, class = "data.frame")

我想创建一个新的数据框,每个“session_user_id”只保留一个观察值,并保留具有最新“submission_time”的观察值。

我唯一想到的方法是创建一个独特用户列表。编写一个循环来查找每个用户的submission_time的最大值,然后编写一个循环以获取该用户和时间的原始分数。

有人能否向我展示在R中更好的方法?

谢谢!

6个回答

13
你可以先按照 submission_time 对你的 data.frame 进行排序,然后再删除所有重复的 session_user_id 条目:
## order by submission_time
df <- df[order(df$submission_time, decreasing=TRUE),]

## remove duplicated user_id
df <- df[!duplicated(df$session_user_id),]

#                            session_user_id raw_score submission_time
#33 198b83b365fef0ed637576fe1bde786fc09817b2       0.2      1390115495
#37 19fd8069c094fb0697508cc9646513596bea30c4       1.0      1389985033
#38 1a3d33c9cbb2aa41515e6ef76f123b2ea8ee2f13       1.0      1389983862
#39 1b64c142b1540c43e3f813ccec09cb2dd7907c14       0.2      1389854836
#28 1803f6c3625c397afb4619804861f75268dfc567       1.0      1389707078
#32 1924cb2ebdf29f052187b9a2d21673e4d314199b       1.0      1389694194
#40 1b7346d13f714c97725ba2e1c21b600535164291       1.0      1389692240

10

使用dplyr表达式很简单:首先按会话ID分组,然后过滤,选择每个组中具有最大时间的行:

library(dplyr)
df %.%
  group_by(session_user_id) %.%
  filter(submission_time == max(submission_time))

如果你不想保留所有的最大时间(如果有重复),你可以这样做:

library(dplyr)
df %.%
  group_by(session_user_id) %.%
  filter(row_number(desc(submission_time)) == 1)

第二个选择最早的时间,因为原始数据按照时间顺序递增排序。 - James
@James 哎呀,已经修复了。但是原始数据的顺序不会影响任何操作。 - hadley

4

我也会提供一个 data.table 的解决方案,并且对于更大的数据进行基准测试,和 dplyr 进行比较:

require(data.table)
DT <- as.data.table(df)
DT[DT[, .I[which.max(submission_time)], by=list(session_user_id)]$V1]

我假设OP只需要一个观察值,即使有多个相同的“最大”值。如果不是这样,请查看下面的f2函数。


对比较大数据的基准测试 vs dplyr

在更大的数据上与@hadley的dplyr解决方案进行基准测试。我假设有大约50e3个用户ID和总共1e7行。

require(data.table)  # 1.8.11 commit 1142
require(dplyr)       # latest commit from github
set.seed(45L)
DT <- data.table(session_user_id = sample(paste0("id", 1:5e4), 1e7, TRUE), 
                 raw_score = sample(10, 1e7, TRUE), 
                 submission_time = sample(1e5:5e5, 1e7, TRUE))

DF <- tbl_df(as.data.frame(DT))

f1 <- function(DT) {
    DT[DT[, .I[which.max(submission_time)], by=list(session_user_id)]$V1]
}

f2 <- function(DT) {
    DT[DT[, .I[submission_time == max(submission_time)], 
            by=list(session_user_id)]$V1]
}

f3 <- function(DF) {
    DF %.%
        group_by(session_user_id) %.%
        filter(submission_time == max(submission_time))
}

f4 <- function(DF) {
    DF %.%
      group_by(session_user_id) %.%
      filter(row_number(desc(submission_time)) == 1)
}

以下是时间记录,每个数据都是至少三次运行的最小值:

system.time(a1 <- f1(DT)) 
#   user  system elapsed
#  1.044   0.056   1.101

system.time(a2 <- f2(DT)) 
#   user  system elapsed
#  1.384   0.080   1.475

system.time(a3 <- f3(DF)) 
#   user  system elapsed
#  4.513   0.044   4.555

system.time(a4 <- f4(DF)) 
#   user  system elapsed
#  6.312   0.004   6.314

如预期的那样,f4 是最慢的,因为它使用了 desc(我猜测这在每个组中涉及排序或排序,是一种比仅获取 maxwhich.max 更加计算密集的操作)。
在这里,a1a4(即使存在多个最大值,只有一个观察结果)给出相同的结果,a2a3 也是如此(所有最大值)。
在这里,data.table 至少快了 3 倍(比较 a2a3),比较 f1f4 时快了约 5.7 倍。

我使用dplyr函数的时间更好(f3和f4分别为1.8和2.6),但我也使用CRAN版本,同样适用于data.table(我从中获得稍微慢一些的时间)。你的配置如何?我正在运行i7-3930k。 - Brandon Bertelsen
没有 data.table 的时间数据 (来自 1.8.11 提交记录 1142),很难比较 1.8 和 2.6。我猜你的电脑上 1.101 和 1.475 也会快得多。所以,它们差不多。 - Arun
我已在Mac OS 10.9和Linux机器(Debian Linux 3.2.0-4-amd64 x86_64)上进行了测试。您使用的data.table版本是哪个? - Arun
正如我所提到的,最近修复了一些(愚蠢的)聚合过程中的错误。因此,有必要检查1.8.11提交>= 1142的时间。 - Arun
Brandon,没问题。很高兴能帮忙。如果你有任何问题,请随时在SO或data.table邮件列表上发布。 - Arun
显示剩余2条评论

2
您可以使用“plyr”包来汇总数据。类似这样的内容应该有效。
max_subs<-ddply(df,"session_user_id",summarize,max_sub=max(submission_time))

ddply接收一个数据框并返回一个数据框,这将给你想要的用户和提交时间。

要返回与此对应的原始数据框行,可以执行以下操作:

df2<-df[df$session_user_id %in% max_subs$session_user_id & df$submission_time %in% max_subs$max_sub,]

我想知道与sgibbs答案中的典型排序和去重相比,这个会有多快。 - Brandon Bertelsen

2

首先按session_user_id查找最大提交时间。这个表会根据session_user_id是唯一的。

然后,只需将其合并(SQL术语:内连接)回您的原始表格,连接submission_time和session_user_id(R会自动选择两个数据框之间的公共名称)。

maxSessions<-aggregate(submission_time~session_user_id , df, max)
mySubset<-merge(df, maxSessions)
mySubset #this table has the data your are looking for

如果你想要更快的速度,而且数据集很大,那么可以看一下这篇文章:如何在R中按组对数据进行汇总? data.tableplyr 是不错的选择。


1

这只是一个扩展的评论,因为我对每个解决方案的速度有兴趣。

library(microbenchmark)
library(plyr)
library(dplyr)
library(data.table)

df <- df[sample(1:nrow(df),10000,replace=TRUE),] # 10k records

fun.test1 <- function(df) {
  df <- df[order(df$submission_time, decreasing = TRUE),]
  df <- df[!duplicated(df$session_user_id),]
  return(df)
}

fun.test2 <- function(df) { 
  max_subs<-ddply(df,"session_user_id",summarize,max_sub=max(submission_time))
  df2<-df[df$session_user_id %in% max_subs$session_user_id & 
          df$submission_time %in% max_subs$max_sub,]
  return(df2)
}

fun.test3 <- function(df) {
  df <- df %.%
    group_by(session_user_id) %.%
    filter(submission_time == max(submission_time))
  return(df)
}

fun.test4 <- function(df) {
  maxSessions<-aggregate(submission_time~session_user_id , df, max)
  mySubset<-merge(df, maxSessions)
  return(mySubset)
}

fun.test5 <- function(df) { 
  df <- df[df$submission_time %in% by(df, df$session_user_id,
           function(x) max(x$submission_time)),]
  return(df)
}

dt <- as.data.table(df) # Assuming you're working with data.table to begin with
# Don't know a lot about data.table so I'm sure there's a faster solution
fun.test6 <- function(dt) { 
  dt <- unique(
    dt[,
       list(raw_score,submission_time=max(submission_time)),
       by=session_user_id]
    )
  return(dt)
}

看起来对于小数据(低于1k),使用!duplicated()的最基本解决方案以显着优势获胜,其次是dplyr。对于大样本(超过1k),dplyr获胜。

microbenchmark(
 fun.test1(df),
 fun.test2(df),
 fun.test3(df),
 fun.test4(df),
 fun.test5(df),
 fun.test6(dt)
)

         expr        min          lq     median         uq        max neval
 fun.test1(df)   2476.712   2660.0805   2740.083   2832.588   9162.339   100
 fun.test2(df)   5847.393   6215.1420   6335.932   6477.745  12499.775   100
 fun.test3(df)    815.886    924.1405   1003.585   1050.169   1128.915   100
 fun.test4(df) 161822.674 167238.5165 172712.746 173254.052 225317.480   100
 fun.test5(df)   5611.329   5899.8085   6000.555   6120.123  57572.615   100
 fun.test6(dt) 511481.105 541534.7175 553155.852 578643.172 627739.674   100

这里是否有data.table的解决方案,还是你只是出于习惯加载它?请提供更大的数据 :)。 - Arun
哈哈,fun.test6是“我的”data.table解决方案,但正如我所评论的那样,我相信有更快的方法来做到这一点。 - Brandon Bertelsen
哦,该死,我没想到要往下滚动...对不起。我会检查的。 - Arun
重新运行了10k条记录,肯定有更快的方法使用data.table,请指点我,Arun。 - Brandon Bertelsen
1
BrandonпјҢжҲ‘е·Із»ҸдҪҝз”Ёdplyrж·»еҠ дәҶдёҖдёӘзӯ”жЎҲе’ҢеҹәеҮҶжөӢиҜ•гҖӮжӮЁеҸҜд»ҘиҝҗиЎҢиҜҘд»Јз Ғ并йҮҚж–°жЈҖжҹҘж—¶й—ҙпјҲдҪҶеҝ…йЎ»жҳҜд»Һ1.8.11зҡ„>=1142жҸҗдәӨпјҢйӮЈйҮҢжңүзӣёеҪ“еӨҡзҡ„й”ҷиҜҜдҝ®еӨҚе’ҢдјҳеҢ–пјүгҖӮ - Arun

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接