【问题标题】:data.table alternative for slow group_by() and case_when() function慢速 group_by() 和 case_when() 函数的 data.table 替代方案
【发布时间】:2019-03-13 11:01:31
【问题描述】:

在我的数据中,如果订单包含某种产品,我有客户 ID、订单日期和指示符。 我想给每个客户一个指标,如果他的第一个订单包含这种类型的产品。但是因为我的数据很大,所以我不能使用 group_by 和 case_when,因为它太慢了。我想我可以通过使用 data.table 来加快速度。

你能给我指出一个解决方案吗?直到现在我还没有与 data.table 有任何联系......

# generate data
id <- round(rnorm(3000, mean = 5000, 400),0)
date <- seq.Date(as.Date("2018-01-01"), as.Date("2018-12-31"), "day")
date <- sample(date, length(id), replace = TRUE)
indicator <-  rbinom(length(id), 1, 0.5)

df <- data.frame(id, date, indicator)
df$id <- as.factor(df$id)

# Does the first Order contain X?
df <- df %>% group_by(id) %>% mutate(First_Order_contains_x = case_when(
  date == min(date) & indicator == "1" ~ 1,
  TRUE ~ 0
)) %>% ungroup() 

# If first order > 1 ==> all orders get 1 // 
df <- df %>% group_by(id) %>% mutate(Customer_type = case_when(
  sum(First_Order_contains_x) > 0 ~ "Customer with X in first order",
  TRUE ~ "Customer without x in first order"
)) %>% ungroup() 

【问题讨论】:

  • 谢谢,我改了!
  • 当您有多个选择时,您应该使用case_when。这里你只有两个,所以你可以在mutate 中尝试First_Order_contains_x = as.integer(date == min(date) &amp; indicator == "1")

标签: r dplyr data.table


【解决方案1】:

另一种方式:

library(data.table)
DT = data.table(df[, 1:3])

lookupDT = DT[, .(date = min(date)), by=id]
lookupDT[, fx := DT[copy(.SD), on=.(id, date), max(indicator), by=.EACHI]$V1]

DT[, v := "Customer without x in first order"]
DT[lookupDT[fx == 1L], on=.(id), v := "Customer with X in first order"]

# check results
fsetequal(DT[, .(id, v)], data.table(id = df$id, v = df$Customer_type))
# [1] TRUE

如果您想进一步提高速度,请查看?IDate

由于an open issue,需要.SD 上的copy

【讨论】:

    【解决方案2】:

    以下是使用 dplyr 更有效地改进现有代码的方法:

    lookup = data.frame(First_Order_contains_x = c(TRUE, FALSE), 
                        Customer_Type = c("Customer with X in first order", 
                                          "Customer without x in first order"))
    
    df %>% 
      group_by(id) %>% 
      mutate(First_Order_contains_x = any(as.integer(date == min(date) & indicator == 1))) %>% 
      ungroup() %>% 
      left_join(lookup, by = "First_Order_contains_x")
    
    # A tibble: 3,000 x 5
       id    date       indicator First_Order_contains_x Customer_Type                    
       <fct> <date>         <dbl> <lgl>                  <fct>                            
     1 5056  2018-03-10         1 TRUE                   Customer with X in first order   
     2 5291  2018-12-28         0 FALSE                  Customer without x in first order
     3 5173  2018-04-19         0 FALSE                  Customer without x in first order
     4 5159  2018-11-13         0 TRUE                   Customer with X in first order   
     5 5252  2018-05-30         0 TRUE                   Customer with X in first order   
     6 5200  2018-01-20         0 FALSE                  Customer without x in first order
     7 4578  2018-12-18         1 FALSE                  Customer without x in first order
     8 5308  2018-03-24         1 FALSE                  Customer without x in first order
     9 5234  2018-05-29         1 TRUE                   Customer with X in first order   
    10 5760  2018-06-12         1 TRUE                   Customer with X in first order   
    # … with 2,990 more rows
    

    【讨论】:

    • 谢谢,我把这个任务从 739 秒缩短到了 139 秒。另一个答案中的 data.table 方法甚至更快(19 秒),但结果并不相同。
    【解决方案3】:

    另一个data.table 方法。首先对数据进行排序,以便第一个日期是最早的日期,然后我们可以使用第一个指标来测试条件。然后,将逻辑转换为整数(FALSE -> 1TRUE -> 2)并使用字符向量映射到所需的输出。

    library(data.table)
    setDT(df)
    setorder(df, id, date)
    map <- c("Customer without x in first order", "Customer with X in first order")
    df[, idx := 1L+any(indicator[1L]==1L), by=.(id)][,
        First_Order_contains_x := map[idx]]
    

    如果原始订单很重要,我们可以使用df[, rn := .I] 存储原始订单,最后使用setorder(df, rn)

    数据:

    set.seed(0L)
    id <- round(rnorm(3000, mean = 5000, 5),0)
    date <- seq.Date(as.Date("2018-01-01"), as.Date("2018-12-31"), "day")
    date <- sample(date, length(id), replace = TRUE)
    indicator <-  rbinom(length(id), 1, 0.5)
    
    df <- data.frame(id, date, indicator)
    df$id <- as.factor(df$id)
    

    【讨论】:

      猜你喜欢
      • 2019-04-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多