【问题标题】:Counting rows until a condition is met in R - NAs before the condition is met在满足条件之前计算行直到满足 R - NA 中的条件
【发布时间】:2020-08-29 09:09:38
【问题描述】:

首先,我对 R 有点陌生,并且在管理一些时间序列数据时遇到了麻烦。我找到了一个可行的解决方案(代码如下),但在较大的数据集上速度非常慢(750k 行上的 1 个变量需要 35 分钟)。

我想要实现的是,每当USAGE 的值超过某个预定义的值 (usage_limit) 时,它就会开始计算行数,直到它再次超过相同的值,然后重置计数器。对于每个客户端,它以 NA 开头并且是 NA,直到它通过 usage_limit,此时计数器更改为 0。如果当计数器已更改为 0 时,NA 现在出现在 USAGE 中,它会正常计数。或者更简单地说,我正在尝试创建一个变量来显示过去USAGE 超过usage_limit 的行数(或者在我的情况下是几个月)。

这是用于计算USAGE_35PCT_MTH 的虚拟数据和预期输出和循环。这是在 R 3.5.1、lubridate 1.7.4 和 tidyverse 1.3.0 上完成的

library(lubridate)
library(tidyverse)

dummy_tb <- tibble("USER_ID"=c("000001", "000001", "000001", "000001", "000001", "000001", "000001", "000001", "000001", "000001", "000001", "000001", "000001", "000001", "000001", "200000", "200000", "200000", "200000", "200000", "200000", "200000", "200000"),
                   "REFERENCE_DATE"=c("31.01.2016", "29.02.2016", "31.03.2016", "30.04.2016", "31.05.2016", "30.06.2016", "31.07.2016", "31.08.2016", "30.09.2016", "31.10.2016", "30.11.2016", "31.12.2016", "31.01.2017", "28.02.2017", "31.03.2017", "31.03.2014", "30.04.2014", "31.05.2014", "30.06.2014", "31.07.2014", "31.08.2014", "30.09.2014", "31.10.2014"),
                   "USAGE"=c(0.30, 0.35, 0.34, 0.38, 0.40, 0.70, 0.78, 0.95, 0.36, 0.22, 0.11, 0.01, 0.1, 0.1, 0.1, NA, 0.36, 0.2, NA, 0.2, 0.2, NA, 0.2),
                   "USAGE_35PCT_MTH"=c(NA, 0, 1, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, NA, 0, 1, 2, 3, 4, 5, 6))

dummy_tb$REFERENCE_DATE <- as_datetime(dummy_tb$REFERENCE_DATE, format="%d.%m.%Y")
dummy_tb$REFERENCE_DATE <- as_date(dummy_tb$REFERENCE_DATE)

dummy_tb <- dummy_tb %>%
    arrange(USER_ID, REFERENCE_DATE) %>%
    mutate("USAGE_35PCT_MTH"=NA)

counter <- NA
user_curr <- ""
user_prev <- ""
usage_limit <- 0.35


for (row in 1:nrow(dummy_tb)){
    user_curr <- dummy_tb[row, "USER_ID"]
    if (user_curr != user_prev ) {
        counter <- NA
    }

    checking_value <- dummy_tb[row, "USAGE"]

    if (!is.na(checking_value)){
        if (checking_value >= usage_limit) {
            counter <- 0
        }
    }
    dummy_tb[row, "USAGE_35PCT_MTH"] <- counter
    counter <- counter + 1
    user_prev <- user_curr 
}

所以我的问题是,有没有办法加快速度?我一直在想办法使用 Dplyr,但还没有找到成功。

感谢您的帮助!

【问题讨论】:

    标签: r dplyr


    【解决方案1】:

    我只想添加一个附录,我在第一个问题中没有指定。虽然 Ronak Shah 的 anwser 对最初的问题非常有效,但我遇到了一个问题,即 USER_ID 在整个 data.frame 中具有所有 NA 值。在 Ronak 的 anwser 中,它通常会从 0 计数到用户拥有的行数。 在这种情况下,我想拥有 NA 值。我只是添加了几行来满足这个要求。

    library(dplyr)
    
    dummy_tb %>%   
        #Replace `NA` with 0   
        mutate(USAGE = replace(USAGE, is.na(USAGE), 0)) %>%   
        #Group by USER_ID   
        group_by(USER_ID) %>%  
        #Create a new group which resets everytime USAGE is greater than usage_limit
        group_by(temp = cumsum(USAGE >= usage_limit), add = TRUE) %>%   
        #Create an index
        mutate(out = row_number() - 1) %>%
        group_by(USER_ID) %>%
        #Replace with NA values before first usage_limit cross.
        mutate(out = replace(out, row_number() < which.max(USAGE >= usage_limit), NA)) %>%
        #Ungroup to reset grouping
        ungroup() %>%
        #group by USER_ID again
        group_by(USER_ID) %>%
        #check if all USAGE values are NA by USER_ID
        mutate(out_temp = all(is.na(USAGE))) %>%
        #replace where out_temp == TRUE
        mutate(out, replace(out, out_temp, NA))
    

    编辑:

    同样,如果USAGE 从未跨越usage_limit,也会出现问题。它通常计算月份,这应该是 NA,因为 USAGE 从未超过 usage_limit。我添加了另一个与之前类似的检查,只要USER_ID 的所有temp 值都是0,因为这意味着它从未改变过它也从未跨越usage_limit 的值。

    最后添加了这些行

        ungroup() %>%
        group_by(USER_ID) %>%
        mutate(out_temp = all(temp==0) %>%
        mutate(out, replace(out, out_temp, NA)) %>%
        ungroup()
    

    【讨论】:

      【解决方案2】:

      这是dplyr 的一种方式:

      library(dplyr)
      
      dummy_tb %>%
        #Replace `NA` with 0
        mutate(USAGE = replace(USAGE, is.na(USAGE), 0)) %>%
        #Group by USER_ID
        group_by(USER_ID) %>%
        #Create a new group which resets everytime USAGE is greater than usage_limit
        group_by(temp = cumsum(USAGE >= usage_limit), add = TRUE) %>%
        #Create an index
        mutate(out = row_number() - 1) %>%
        group_by(USER_ID) %>%
        #Replace with NA values before first usage_limit cross.
        mutate(out = replace(out, row_number() < which.max(USAGE >= usage_limit), NA))
      

      返回:

      #   USER_ID REFERENCE_DATE USAGE USAGE_35PCT_MTH temp out
      #1   000001     31.01.2016  0.30              NA    0  NA
      #2   000001     29.02.2016  0.35               0    1   0
      #3   000001     31.03.2016  0.34               1    1   1
      #4   000001     30.04.2016  0.38               0    2   0
      #5   000001     31.05.2016  0.40               0    3   0
      #6   000001     30.06.2016  0.70               0    4   0
      #7   000001     31.07.2016  0.78               0    5   0
      #8   000001     31.08.2016  0.95               0    6   0
      #9   000001     30.09.2016  0.36               0    7   0
      #10  000001     31.10.2016  0.22               1    7   1
      #11  000001     30.11.2016  0.11               2    7   2
      #12  000001     31.12.2016  0.01               3    7   3
      #13  000001     31.01.2017  0.10               4    7   4
      #14  000001     28.02.2017  0.10               5    7   5
      #15  000001     31.03.2017  0.10               6    7   6
      #16  200000     31.03.2014  0.00              NA    0  NA
      #17  200000     30.04.2014  0.36               0    1   0
      #18  200000     31.05.2014  0.20               1    1   1
      #19  200000     30.06.2014  0.00               2    1   2
      #20  200000     31.07.2014  0.20               3    1   3
      #21  200000     31.08.2014  0.20               4    1   4
      #22  200000     30.09.2014  0.00               5    1   5
      #23  200000     31.10.2014  0.20               6    1   6
      

      【讨论】:

      • 谢谢!这解决了我的问题,并将时间从约 35 分钟减少到 4 秒。非常感谢!
      猜你喜欢
      • 1970-01-01
      • 2015-03-21
      • 1970-01-01
      • 2022-12-06
      • 1970-01-01
      • 1970-01-01
      • 2013-12-28
      • 2020-03-07
      • 1970-01-01
      相关资源
      最近更新 更多