在一组中提取变量的第一次出现的行

6
我有一个海量数据集(超过100个变量的200万行,以下是小样本)。对于每个subj_trial组,我想找到在message中包含".wav"的每个唯一变量的第一次出现。它应该只包含而不是结束(i.e. *.wav),因为有些行在message字段中有一堆信息(在示例中未显示,抱歉)。
输出只需要三列DataFrame即可,但这不是必须的。我将稍后需要使用时间戳列进行分析。
我找到了这个问题: 从数据框中提取变量的第一个出现的行,但是为了生命,在我的例子中我无法让它起作用。
下面是一些示例数据:
   subj_trial     message timestamp
1         1_1 message 459    755616
2         1_1           .    755618
3         1_1   test1.wav    755662
4         1_1           .    765712
5         1_1   test1.wav    767918
6         1_2           .    769342
7         1_2   test2.wav    775662
8         1_2           .    786412
9         1_2   test2.wav    797460
10        1_2           .    807626
11        1_3   test3.wav    817794
12        1_3  warning 11    827960
13        2_1 message 481    817313
14        2_1   test1.wav    817347
15        2_1           .    834959
16        2_1   test1.wav    855007
17        2_1           .    880107
18        2_2           .    895723
19        2_2   test2.wav    922671
20        2_2           .    958003
21        2_2   test2.wav    994385
22        2_3           .   1016217
23        2_3   test3.wav   1036899
24        2_3           .   1047331
25        2_3   test3.wav   1142527

这只是我处理的一个非常小的示例。对于每个subj_trial组,可能有3000行以上,而且有超过700个组。
以下是我想要的一个示例。
  subj_trial   message timestamp
1        1_1 test1.wav    755662
2        1_2 test2.wav    775662
3        1_3 test3.wav    817794
4        2_1 test1.wav    817347
5        2_2 test2.wav    922671
6        2_3 test3.wav   1036899

我已经想出了如何通过这样做来获取整个数据集中 message 的唯一值:

unique_message <- df[match(unique(df$message), df$message),]

但是我不知道如何按组进行操作。我也尝试使用dplyr包中的group_by,但是也无法使其正常工作。请朋友们给予帮助并指引正确的方向。谢谢!


@SerbanTanasa 这样做是没有帮助的,因为这将是一个由25行构成的只有一个组的数组,其中message字段大多数都是“.”,而且只有一个.wav值的实例。我提供的示例是很好的一个。 - Elizabeth Crutchley
@SerbanTanasa 好的,我明白你关心的是格式而不是内容。谢谢你让我知道。 - Elizabeth Crutchley
3个回答

6

如果您感兴趣,这里还有一个 dplyr 的解决方案:

dat %>%
  filter(grepl("\\.wav", message)) %>%
  group_by(subj_trial) %>%
  top_n(n=1, wt=desc(timestamp))

首先,将数据过滤为仅包含消息列中的*.wav。然后按主题试验分组,并返回时间戳最小的顶部结果。这假设您想要最小的时间戳,而不一定是数据集中的第一个时间戳(即如果具有较大时间戳的记录首先出现,则不会返回该记录)。我不确定您要找哪个,但也许在您的情况下没有区别。
由于我总是对data.table和dplyr方法之间的效率差异感到好奇,所以我进行了microbenchmark测试。看起来,在这种情况下,data.table具有轻微的速度优势:
library(microbenchmark)
library(data.table)

set.seed(1)
dat <- data.frame(subj_trial=paste0(sample(1:20,1e6,replace=TRUE),"_",sample(1:20,1e6,replace=TRUE)),
                  message=sample(c(".wav","others"), 1e6, replace=TRUE),
                  timestamp=round(seq(from=1000, to=9142527, length.out = 1e6))) 

dat2 <- dat
setDT(dat2)

microbenchmark({dat %>%
  filter(grepl("\\.wav", message)) %>%
  group_by(subj_trial) %>%
  top_n(1, wt=desc(timestamp))},
  {dat2[grepl("\\.wav", message), .SD[1], by=subj_trial]})

结果:

Unit: milliseconds

expr

dat %>% filter(grepl("\\\\.wav", message)) %>% group_by(subj_trial) %>% top_n(1, wt = desc(timestamp))
dat2[grepl("\\\\.wav", message), .SD[1], by = subj_trial] 
      min       lq     mean   median       uq      max neval cld
 332.9693 357.7426 387.2245 367.6443 380.9935 637.9223   100   b
 263.0292 272.8627 293.4976 281.4568 285.7699 582.9954   100  a 

嘿,感谢你的帮助!三年过去了,它仍然运行得非常出色! - Will M

3

同时使用data.table,但使用更简洁的表述:

setDT(DT)
DT[,.SD[grep("\\.wav",message)[1]],by=subj_trial]

编辑:如下方的评论所建议的那样,

DT[grepl("\\.wav", message), .SD[1], by=subj_trial]

可能会更快,因为它使用布尔逻辑和优化的I子集。

.SD是一个data.table,包含DT每个组的数据子集,不包括任何在by(或keyby)中使用的列。

by有点像SQL中的group by运算符。它指定了分组列。

grep(pattern, x)返回x中所有匹配pattern的索引,其中x是向量。 \\表示在.wav之前,防止grep将.视为特殊字符(在grep的解析中,未转义的.表示“任何内容”)。

vector_name[1]返回名为vector_name的向量的第一个元素。它可以用于上面的grep函数的结果。

data.table公式为DT[I,J,by] - I是子集或连接,J是要执行的操作,by是分组元素。在我们的例子中,I被忽略(因此前面有,),因为我们想在全部数据上进行操作。 J是对所有.SD列的操作。by是您希望结果按其分组的列。


抢我一步之先了 - 我本来要建议 dat[grepl("\\.wav", message), .SD[1], by=subj_trial] - thelatemail
刚刚在 set.seed(1); dat <- data.table(subj_trial=sample(1:1e5,1e6,replace=TRUE), message=sample(c(".wav","others"), 1e6, replace=TRUE)) 上进行了快速基准测试。根据我上面的评论,将 grepl 移到 data.table 的 i 中可以使其运行速度大大提高(30秒 vs. 0.3秒)。 - thelatemail

1
使用 data.table:
library(data.table)
setDT(DT)
DT[,{
  id=head(grep("\\.wav",message),1)
  list(message=message[id],timestamp=timestamp[id])
},subj_trial]

#    subj_trial   message timestamp
# 1:        1_1 test1.wav    755662
# 2:        1_2 test2.wav    775662
# 3:        1_3 test3.wav    817794
# 4:        2_1 test1.wav    817347
# 5:        2_2 test2.wav    922671
# 6:        2_3 test3.wav   1036899

4
你能否稍微解释一下这是如何工作的?看起来非常难懂。 - Serban Tanasa

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