首页 > 解决方案 > 删除与同一列上不同值的 5 分钟窗口内发生的与单个列上的值匹配的记录

问题描述

我有一个看起来像这样的数据框:

require(data.table)
require(tidyverse)

df <- as.data.frame(matrix(c(123, "2018-01-05 09:09:02", "Mobile",
                         123, "2018-01-06 11:11:15", "Organic",
                         123, "2018-01-07 13:24:45", "Email",
                         123, "2018-01-07 13:24:55", "Organic",
                         321, "2018-01-05 15:15:29", "Organic",
                         989, "2018-01-08 08:09:21", "Feeds",
                         989, "2018-01-08 08:09:55", "Organic",
                         989, "2018-01-10 10:21:40", "Email"), nrow = 8,
                       ncol = 3, byrow = TRUE, dimnames = list(NULL, c("customer_id", "entry_time",
                                                                       "channel"))))

df$entry_time <- as.POSIXct(df$entry_time)


 df
 customer_id          entry_time channel
1         123 2018-01-05 09:09:02  Mobile
2         123 2018-01-06 11:11:15 Organic
3         123 2018-01-07 13:24:45   Email
4         123 2018-01-07 13:24:55 Organic
5         321 2018-01-05 15:15:29 Organic
6         989 2018-01-08 08:09:21   Feeds
7         989 2018-01-08 08:09:55 Organic
8         989 2018-01-10 10:21:40   Email

我想做的是为每个客户删除在非有机记录的五分钟窗口内发生的所有“有机”记录。

换句话说,我想删除所有记录,其中:1) 频道 = 有机和 2) entry_time < 5 分钟从上一条记录中删除,3) 上一条记录的频道!= 有机。我需要能够为每个客户 ID 执行此操作。

我想要的输出如下所示:

df_desired <- as.data.frame(matrix(c(123, "2018-01-05 09:09:02", "Mobile",
                         123, "2018-01-06 11:11:15", "Organic",
                         123, "2018-01-07 13:24:45", "Email",
                         321, "2018-01-05 15:15:29", "Organic",
                         989, "2018-01-08 08:09:21", "Feeds",
                         989, "2018-01-10 10:21:40", "Email"), nrow = 6,
                       ncol = 3, byrow = TRUE, dimnames = list(NULL, c("customer_id", "entry_time",
                                                                       "channel"))))

df_desired$entry_time <- as.POSIXct(df_desired$entry_time)

df_desired
customer_id          entry_time channel
1         123 2018-01-05 09:09:02  Mobile
2         123 2018-01-06 11:11:15 Organic
3         123 2018-01-07 13:24:45   Email
4         321 2018-01-05 15:15:29 Organic
5         989 2018-01-08 08:09:21   Feeds
6         989 2018-01-10 10:21:40   Email

我可以用下面的嵌套循环来做到这一点(很抱歉让你接触到这个怪物)。

dat_splt <- split(df, df$customer_id)


for (h in 1:length(dat_splt)){
dat_splt[[h]]$prox_flag <- 0
if (nrow(dat_splt[[h]]) == 1)
{next}
else
{for (g in 2:nrow(dat_splt[[h]])){
if (dat_splt[[h]][g,]$channel != "Organic")
{next}
else if (dat_splt[[h]][g-1,]$channel != "Organic" &
         as.numeric((difftime(dat_splt[[h]][g,]$entry_time, dat_splt[[h]][g-1,]$entry_time, units = "mins")) < 5))
{dat_splt[[h]][g,]$prox_flag <- 1}
else
{next}
}}
}

dat <- rbindlist(dat_splt)

dat <- dat %>%
   filter(prox_flag != 1)

不用说,这不能很好地扩展。有人可以帮我解开这个棘手的解决方案,使其变得更实用吗?

非常感谢提前。

标签: r

解决方案


R 的美妙之处在于几乎所有的操作都是向量化的,因此你可以同时比较多个事物,并且不需要 for 循环。

在这种情况下,您必须直接将所有值与之前的值进行比较,这可以通过比较来完成df[-1,]df[-nrow(df),]即第二行与第一行进行比较,第三行与第二行进行比较,依此类推。
只有第一行是一个例外:它总是需要保留。

另外,我认为没有真正需要按客户拆分,或者它们可以交错吗?如果没有,看看 customer_id 是否与上面的行不同就足够了。一次运行的代码:

nr <- nrow(df)
df_desired <- rbind(
    df[1,],
    df[-1,][!(df$customer_id[-1]==df$customer_id[-nr] &
              df$channel[-1]=='Organic' &
              df$channel[-nr]!='Organic' &
              as.numeric(df$entry_time[-1]-df$entry_time[-nr],
                   units='mins')<5)
            ,])

最后一句话:我不知道你从哪里得到你的数据,但首先存储为矩阵然后使用as.data.frame通常不是最好的主意。您用于将c数据提供给矩阵意味着所有内容都被强制转换为同一类,这意味着所有数字都变为字符。虽然 data.frame 可以很好地处理不同的类。
在这种情况下,您只是使用 'id' 作为标识符,但如果您有想要为数字的列,则需要将它们转换回来,就像您使用 POSIXct 所做的那样。


推荐阅读