计数行,直到满足条件之前在R-NA中满足条件

时间:2020-05-13 08:49:34

标签: r dplyr

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

我想要实现的是,每次USAGE值超过某个预定义值(usage_limit)时,它就会开始对行进行计数,直到再次超过相同的值为止。重置计数器。对于每个客户端,当计数器更改为0时,它以NA开头,并且一直为NA,直到通过usage_limit为止。如果计数器已经更改为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的合作方式,但还没有取得成功。

感谢帮助!

2 个答案:

答案 0 :(得分: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

答案 1 :(得分:0)

我只想添加一个附录,我在第一个问题中没有指定。尽管Ronak Shah的解决方案为最初的问题做出了出色的工作,但我遇到一个问题,其中USER_ID在整个NA中具有所有data.frame的值。在Ronak的答案中,通常是从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,就会出现问题。由于USAGE从未超过usage_limit,因此通常计算该月为NA。我像以前一样添加了另一个类似的检查,只是temp的所有USER_ID值都是0,因为这意味着它从未更改过的值也从未超过usage_limit

最后添加了这些行

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