【问题标题】:Merge rows with messages in same timespan grouped by sender按发件人分组的同一时间跨度内的消息合并行
【发布时间】:2021-05-30 13:03:51
【问题描述】:

我有一个数据框,其中包含来自 whatsapp 的发件人的消息。通常,人们倾向于在whatsapp中使用不止一条消息来说些什么。我的目标是将在大约 5 分钟的时间范围内发送的消息合并为一行。这必须按发件人分组,因为数据帧包含多个发件人。我的数据框如下所示:

Message             Time                    Sender
hello               2018-12-14 16:27:00     300
how are you?        2018-12-14 16:29:00     300
do you reply?       2018-12-15 12:00:00     300
Hi                  2018-12-14 16:30:00     200
Hi                  2018-12-15 16:36:00     200

现在我希望它看起来像这样:

Message             Time                    Sender
hello how are you?  2018-12-14 16:27:00     300
do you reply?       2018-12-15 12:00:00     300
Hi                  2018-12-14 16:30:00     200
Hi                  2018-12-15 16:3600      200

第一个和第二个已合并。到目前为止,我的尝试是将 group_map 与 reduce 一起使用,但我似乎找不到解决方案。

joinedMessageTable <- messageTable %>% group_by(Sender) %>% group_map(function(table, sender) {
    Reduce(function(firstElement, result) {
        value <- (firstElement + 5 *60 > result)
    }, table$time)
})

有人知道怎么解决吗?

【问题讨论】:

  • 如果消息在 5 分钟内发送,但在超过 5 分钟的时间段内发送,该怎么办?示例:来自发件人 300 的消息在 1:05、1:09、1:11 和 1:15 发送。你还想把这四个合并在一起吗?
  • 是的,我想合并这些。我希望它基于时间范围内的最新发送消息。所以 1:05 + 5 = 1:10。包括 1:09。 1:09 + 5 = 1:14。所以包括 1:11。 1:11 + 5 = 1:16,所以也包括 1:15。
  • 如果您以更易于复制的格式提供数据,最好使用dput 提供帮助。阅读how to give a reproducible example

标签: r time group-by merge


【解决方案1】:

您可以使用ceiling_date 来创建每 5 分钟一次的群组 -

library(dplyr)
library(lubridate)

messageTable %>%
  group_by(Sender, Time = ceiling_date(mdy_hm(Time), unit = '5 mins')) %>%
  summarise(Message = paste0(Message, collapse = ' ')) %>%
  ungroup

如果您想考虑连续值之间的差异,可以持续 5 分钟 -

df %>%
  mutate(Time = mdy_hm(Time)) %>%
  group_by(Sender) %>%
  mutate(group = cumsum(difftime(Time, 
                   lag(Time, default = first(Time)), units = 'mins') > 5)) %>%
  group_by(group, .add = TRUE) %>%
  summarise(Message = paste0(Message, collapse = ' ')) %>%
  ungroup

【讨论】:

  • 我使用来自基本 R 的 cut 进行分组。它部分工作。这些组是硬切的。应该合并的消息,不要合并。这是针对评论中描述的 Wimpel 的情况
  • 你能用difftime试试我编辑的答案吗?
  • 是的,这似乎有效!谢谢!不过,我确实有更多的专栏。我该如何保留它们?
  • 您可以在末尾使用mutate 而不是summarise,以便为现有数据集添加一个新列。您还可以使用select(-group) 等删除不需要的列。
  • 是的,也许您可​​以使用%&gt;% distinct(Sender, Message, .keep_all = TRUE),它将保留每个组合的第一行。
【解决方案2】:

这行得通吗:

library(stringr)
library(dplyr)
df %>% group_by(Sender) %>% 
   mutate(flag = +(difftime(lead(Time), Time, units = 'min') < 5), flag = replace_na(flag, 0),
          Message = case_when(flag == 1 ~ str_c(Message, lead(Message), sep = ' '), TRUE ~ Message),
          Time = case_when(lag(flag) == 1 & flag == 0 ~ lag(Time), TRUE ~ Time)) %>% select(-flag) %>% filter(!duplicated(Time))
# A tibble: 4 x 3
# Groups:   Sender [2]
  Message            Time                Sender
  <chr>              <dttm>               <dbl>
1 hello how are you? 2018-12-14 16:27:00    300
2 do you reply?      2018-12-15 12:00:00    300
3 Hi                 2018-12-14 16:30:00    200
4 Hi                 2018-12-15 16:36:00    200

【讨论】:

  • 它给出以下错误:“mutate() 输入 flag 的问题。✖字符串不是标准的明确格式ℹ输入 flag+(difftime(lead(time), time, units = "min") &lt; 5)。ℹ错误发生在第 1 组:发件人 = 1。”我的时间是 POSIXct 格式的,可​​能与此有关?
  • @JoostLuijben,连上面的数据都是POSIXct格式。你检查了str() 的数据框了吗?
  • 这给出了以下时间值$ time : chr "2/22/2020, 11:48" "2/26/2020, 13:15" "2/26/2020, 06:21" "1/29/2020, 24:07" ... 我需要将其转换为日期吗?我之前使用以下内容将其更改为正确的格式as.POSIXct(joinedFrame$time, format="%m/%d/%Y, %H:%M")。我对 R 很陌生,所以我不知道它有哪些数据类型
  • 你是对的,我犯了一个错误。 Time 是一个字符串,而不是 POSIXct 类型。我把它转换成posixct
  • 这个解决方案还没有达到预期的效果。如果有一条带有时间戳2018-12-14 16:31:00 的额外消息,则不包括在内。我认为因为铅只需要下一个元素,但我需要所有元素
【解决方案3】:

如果输出对您有帮助,您也可以runner(用于计算滑动窗口/滚动计算的库),

my_df <- read.table(text = "Message             Time                    Sender
'hello'               '2018-12-14 16:27:00'     300
'how are you?'        '2018-12-14 16:29:00'     300
'do you reply?'       '2018-12-15 12:00:00'     300
'Hi'                  '2018-12-14 16:30:00'     200
'Hi'                  '2018-12-15 16:36:00'     200", header = T)

my_df$Time <- as.POSIXct(my_df$Time)

library(tidyverse)
library(runner)

my_df %>% group_by(Sender) %>%
  mutate(msg = runner(Message,
                      idx = Time,
                      k = '5 mins',
                      f = toString))
#> # A tibble: 5 x 4
#> # Groups:   Sender [2]
#>   Message       Time                Sender msg                
#>   <chr>         <dttm>               <int> <chr>              
#> 1 hello         2018-12-14 16:27:00    300 hello              
#> 2 how are you?  2018-12-14 16:29:00    300 hello, how are you?
#> 3 do you reply? 2018-12-15 12:00:00    300 do you reply?      
#> 4 Hi            2018-12-14 16:30:00    200 Hi                 
#> 5 Hi            2018-12-15 16:36:00    200 Hi

reprex package (v2.0.0) 于 2021 年 5 月 30 日创建

【讨论】:

    【解决方案4】:

    我们可以使用

    library(dplyr)
    library(lubridate)
    
    messageTable %>%
      group_by(Sender, Time = ceiling_date(mdy_hm(Time), unit = '5 mins')) %>%
      summarise(Message = toString(Message), .groups = 'drop') 
    

    【讨论】:

      【解决方案5】:

      在结束检查后,@Ronak 给出的解决方案并没有完全奏效。我使用其中的一些代码来制作自己的实现。 Ronak 的代码存在的问题是它使用 false 和 true 进行分组,因此只能制作两组时间。事实上,我需要根据具有累积窗口的时间来拥有多个具有多个发件人的组。

      我提出的解决方案并未真正证实 R 哲学,因此请随意改进。

      my_df <- read.table(text = "Message             Time                    Sender
                             'hello'               '2018-12-14 16:27:00'     300
                             'how are you?'        '2018-12-14 16:34:00'     300
                             'are you good?'       '2018-12-14 16:29:00'     300
                             'do you reply?'       '2018-12-15 12:00:00'     300
                             'Have you received?'  '2018-12-15 16:34:00'     300
                             'are you good?'       '2018-12-15 16:29:00'     300
                             'Hi'                  '2018-12-14 16:30:00'     200
                             'Hi'                  '2018-12-15 16:36:00'     200
                             'Who is this?'        '2018-12-15 16:35:00'     100
                             'Hi'                  '2018-12-15 16:35:00'     100", header = T)
      
      my_df$Time <- as.POSIXct(my_df$Time)
      
      merged_df <- my_df %>% 
        group_by(Sender, Time) %>% 
        mutate(Message = paste0(Message, collapse = " ")) %>%
        distinct(Message, Sender, Time, .keep_all = TRUE) %>%
        group_by(Sender) %>%
        arrange(Time) %>%
        group_modify(function(table, sender) {
          counter <<- 1
          begin <<- first(table$Time)
          for (i in seq_len(nrow(table))) {
            if (isTRUE(abs(difftime(begin, table[i,]$Time, units = "mins")) > 5)) {
              counter <<- counter + 1
            }
            begin <<- table[i,]$Time
            table[i, "counter"] <- counter
          }
          table
        }) %>%
        group_by(Sender, counter, add = TRUE) %>%
        mutate(
          Message = paste0(Message, collapse = ' ')
        ) %>%
        distinct(Sender, counter, .keep_all = TRUE) %>%
        select(-counter)
      

      编辑
      正如 Ronak 指出的那样,上述问题并不是真正的问题。使用安排它可以解决问题。完整的解决方案是:

      df <- joinedFrame %>%
        group_by(Sender) %>%
        arrange(time) %>%
        mutate(group = cumsum(difftime(time,
                                     lag(time, default = first(time)), units = 'mins') > 5)) %>%
        group_by(group, .add = TRUE) %>%
        mutate(English = paste0(English, collapse = ' ')) %>%
        ungroup %>% distinct(Sender, group, .keep_all = TRUE)
      

      我保留我的解决方案和上面的对话以供参考

      【讨论】:

      • The problem Ronak's code has is that it groups using false and true, thus only two groups of time can be made. 这不是真的。我在 false/true 值上使用了cumsum,所以每次遇到TRUE 它都会增加计数。
      • 啊,你是对的。我认为问题在于我的完整数据框没有被订购。使用安排您的解决方案似乎有效
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-10-25
      • 2021-03-30
      • 2017-09-06
      • 2017-09-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多