【问题标题】:Implementing apply using previous rows instead of for loop使用前一行而不是 for 循环实现应用
【发布时间】:2017-05-01 09:43:00
【问题描述】:

我试图避免 for 循环并使用 apply 代替我检测到的后处理标志。

我有一个时间序列,其中有一列显示质量是否正常。数据框如下所示:

n <- 100
tstart <- strptime("12/15/16 16:00:00", "%m/%d/%y %H:%M:%S")
df <- data.frame(Date = tstart + seq(0,n*5-1,5) + sample(seq(0,3,1), n, replace = T),
             Check = sample(c("FLAG", "PASS"), n, replace = T))

# head of df
#         Date           Check
# 1 2016-12-15 16:00:02  FLAG
# 2 2016-12-15 16:00:05  PASS
# 3 2016-12-15 16:00:13  FLAG
# 4 2016-12-15 16:00:17  PASS
# 5 2016-12-15 16:00:22  FLAG
# 6 2016-12-15 16:00:26  FLAG

虽然我不喜欢接收所有FLAGs。我想申请三个条件:

1) 忽略与上一行时间差超过 60 秒的标志

2) 我想保留已经重复了一段时间的标志。

我是这样实现的:

df$Time_Difference <- c(0,as.numeric(diff(df$Date)))
df$Flag_Counter <- 0
desired_rep <- 3
# Start the clock!
ptm <- proc.time()
for (row_index in 2:nrow(df)){
    if (df[row_index, "Time_Difference"] > 60){
        df[row_index, "Flag_Counter"] <- 0
    }
    else {
        if (df[row_index, "Check"] == "PASS"){
            df[row_index, "Flag_Counter"] <- max(0, df[row_index-1, "Flag_Counter"] - 1)
        }
        else {
            df[row_index, "Flag_Counter"] <- min(desired_rep, df[row_index-1, "Flag_Counter"] + 1)
        }
    }
}
# Stop the clock
x <- proc.time() - ptm
print(x[3])

所以,for 循环实际上是获取连续重复desired_rep 次的标志。如果我们在两个FLAGs 之后有一个PASS,则1 是Flag_Counter,最后我们做df[, df$Flag_Counter == 3],我们可以后处理标志。现在,这是非常缓慢的。我想知道我们是否可以使用apply 来加快这项任务。我在Python 中完成了此操作,但我不知道如何访问我的预定义函数中的前几行,然后使用apply。我感谢您的帮助。

【问题讨论】:

  • 这是一个难以重现的示例,因为行之间的时间差不超过 60 秒。另外,你想要的结果是什么?只是一个新列,FlagCounter
  • 涉及随机过程的例子,please add set.seed for reproducibility.

标签: r for-loop apply


【解决方案1】:

试试这个:

desired_rep = 3

# If Time_Difference > 60, 0, otherwise 1 if "Flag", -1 if "Pass"
df$temp = ifelse(df$Check=='FLAG',1,-1)*(df$Time_Difference<=60)

# Do a "cumsum" that's bounded between 0 and 3, and resets to 0 if Time_Difference > 60
df$Flag_Counter = Reduce(function(x,y) max(0, min(desired_rep,x+y))*(y!=0), df$temp, acc=T)

一般来说,Reduce() 在您需要按顺序更新“状态”时很有用,其限制是输入是单个列表/向量(此处为 temp 列)。

【讨论】:

  • 太棒了。我原来的 for 循环需要 180 秒才能在 200k 行数据帧上完成。您的方法在一秒钟内完成!卓越性能的关键是Reduce 功能吗? acc=T 在这里做什么?谢谢。
  • 通过遍历行来访问 data.frame 的元素的成本很高(说实话,不确定原因是什么,但比较 for (i in 1:1000000) {}d = data.frame(x=1:1000000); for (i in 1:1000000) {d[i,]})。如果您查看Reduce 的源代码,您会发现它也使用 for 循环,但使用列表,这样更有效。 (e = as.list(1:1000000); for(i in 1:1000000) {e[[i]]})
  • accumulate=T 导致Reduce 返回所有中间结果,因此您得到一个与x 长度相同的向量。
【解决方案2】:

试试这个:

n <- 100
tstart <- strptime("12/15/16 16:00:00", "%m/%d/%y %H:%M:%S")
df <- data.frame(Date = tstart + seq(0,n*5-1,5) + sample(seq(0,3,1), n, replace = T),
                 Check = sample(c("FLAG", "PASS"), n, replace = T))

desired_rep <- 3 #set the desired repetition limit

您在示例代码中使用的时间是End_Time。我假设这应该是原始数据集中的Date

df$Time_Difference <- c(0,as.numeric(diff(df$Date)))

找到连续的标志。感谢这个post

df$consecutive_flag_count <- sequence(rle(as.character(df$Check))$lengths)

创建一个check_again 列,如果CheckPassTime_Difference 小于60 并且连续desired_rep 小于Check,则该列将返回OK

df$check_again <- ifelse(df$Check == "PASS", "OK", 
 ifelse(df$Time_Difference < 60 & df$consecutive_flag_count >= desired_rep, "CHECK_AGAIN","OK"))

然后您可以轻松过滤到CHECK_AGAIN 项目,如下所示。

df_check_again <- df[df$check_again == "CHECK_AGAIN", ]
> df_check_again
                  Date Check Time_Difference consecutive_flag_count check_again
3  2016-12-15 16:00:11  FLAG               4                      3 CHECK_AGAIN
4  2016-12-15 16:00:18  FLAG               7                      4 CHECK_AGAIN
17 2016-12-15 16:01:23  FLAG               5                      3 CHECK_AGAIN
18 2016-12-15 16:01:26  FLAG               3                      4 CHECK_AGAIN
19 2016-12-15 16:01:30  FLAG               4                      5 CHECK_AGAIN
20 2016-12-15 16:01:37  FLAG               7                      6 CHECK_AGAIN
27 2016-12-15 16:02:10  FLAG               3                      3 CHECK_AGAIN
28 2016-12-15 16:02:18  FLAG               8                      4 CHECK_AGAIN
29 2016-12-15 16:02:20  FLAG               2                      5 CHECK_AGAIN
42 2016-12-15 16:03:27  FLAG               4                      3 CHECK_AGAIN
43 2016-12-15 16:03:33  FLAG               6                      4 CHECK_AGAIN
44 2016-12-15 16:03:38  FLAG               5                      5 CHECK_AGAIN
55 2016-12-15 16:04:33  FLAG               7                      3 CHECK_AGAIN
56 2016-12-15 16:04:36  FLAG               3                      4 CHECK_AGAIN
57 2016-12-15 16:04:41  FLAG               5                      5 CHECK_AGAIN
58 2016-12-15 16:04:45  FLAG               4                      6 CHECK_AGAIN
85 2016-12-15 16:07:02  FLAG               7                      3 CHECK_AGAIN
> 

【讨论】:

  • 感谢您的回复。这是一个很好的解决方案。我认为应该以这种方式更改以下行df$check_again &lt;- ifelse(df$Check == "PASS", "OK", ifelse(df$Time_Difference &lt; 60 &amp; df$consecutive_flag_count &gt;= desired_rep, "CHECK_AGAIN","OK")),以便我们得到正确的答案。
  • 很好的电话,@bikhaab。现在更新答案。
  • 您的解决方案非常快。但是,我没有得到与 for 循环相同的结果。我正在调查原因!
  • 我认为 consecutive_flag_count 不应独立于 Time_Difference 进行评估。例如,在一天结束时,我们可能一直在收到标志,有 8 个小时没有数据,早上继续收到标志。好吧,consecutive_flag_count 将它们放在一起,但是,其中有一个非常大的Time_Difference 打破了结果。稍后申请df$Time_Difference &lt; 60 无济于事,应同时合并。
  • df$consecutive_flag_count_mod &lt;-ifelse(df$Time_Difference &gt; 60, df$consecutive_flag_count &lt;- 1,df$consecutive_flag_count) 添加到代码中,然后从consecutive_flag_count_mod 中获取check_again 是否可以解决问题?我为错误道歉。很高兴删除回复,因为发布了另一个可行的解决方案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-27
  • 2011-10-06
  • 2021-01-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多