R等效于通过行首进行SQL SUM OVER PARTITION的操作

时间:2018-07-29 11:45:07

标签: sql r data.table

尝试在R中复制SQL窗口函数时遇到麻烦,尤其是与创建指定我要求和的前几个月总数的求和有关。

尽管R中的sqldf包允许进行数据操作,但它似乎不支持窗口功能。

我在R中有一些模拟数据

?

对于每行,我想为前两个月(不包括当月)创建一个与客户有关的值的累积总和(Cust_ID)。

这意味着对于每个客户,第1个月和第2个月的行应为空(假设前两个月没有2个行),第3个月应包含该客户的第1个月和第2个月的SalesValue总和,而第4个月应包含包含第2个月和第3个月的总销售价值。

在SQL中,我将使用类似于以下的语法:SUM(SalesValue)OVER(PUSTITION按Cust_ID排序按月DESC行在2个PRECEDING和1个PRECEDING之间)作为PAST_3Y_SALES

在R中是否可以实现这一点-理想情况下使用data.table(为了提高效率)?任何指导将不胜感激。

PS注意:这是模拟数据,在我的“真实”数据中,客户拥有不同的数据量-即某些客户拥有5个月的数据,其他客户具有> 36个月的数据,等等。

5 个答案:

答案 0 :(得分:4)

由于OP已使用data.table,因此使用RcppRoll::roll_sumr范围为data.table的解决方案可以是:

library(data.table)
library(RcppRoll)

# Order on 'Cust_ID' and 'Month'
setkeyv(data_1,c("Cust_ID","Month"))

data_1[, Sum_prev:=shift(roll_sumr(SalesValue, n=2)), by=Cust_ID]

data_1
#    Cust_ID Month StatusCode SalesValue Sum_prev
# 1:       1     1          D       1055       NA
# 2:       1     2          F        669       NA
# 3:       1     3          E        495     1724
# 4:       1     4          D        786     1164
# 5:       2     1          E        445       NA
# 6:       2     2          D        448       NA
# 7:       2     3          F        377      893
# 8:       2     4          E        173      825
# 9:       3     1          F        873       NA
# 10:       3     2          E        995       NA
# 11:       3     3          D        673     1868
# 12:       3     4          F        943     1668

方法是首先计算宽度为2的总和,然后使用data.table::shiftlag来获取具有前两行总和的当前行的先前值。

答案 1 :(得分:2)

这是使用dplyr

的解决方案
library(dplyr)
library(zoo)
as.data.frame(data_1) %>%  group_by(Cust_ID) %>% arrange(Cust_ID, Month) %>%
              mutate(Sum_prev =rollapplyr(SalesValue, list(-(1:2)), sum, fill = NA)) 


# A tibble: 12 x 5
# Groups:   Cust_ID [3]
     Cust_ID Month StatusCode SalesValue Sum_prev
       <dbl> <dbl> <chr>           <dbl>    <dbl>
  1       1     1 D                1055       NA
  2       1     2 F                 669       NA
  3       1     3 E                 495     1724
  4       1     4 D                 786     1164
  5       2     1 E                 445       NA
  6       2     2 D                 448       NA
  7       2     3 F                 377      893
  8       2     4 E                 173      825
  9       3     1 F                 873       NA
 10       3     2 E                 995       NA
 11       3     3 D                 673     1868
 12       3     4 F                 943     1668

使用data.table:

library(data.table)
library(zoo)
#dt <- data_1[order(Cust_ID,Month)]
#dt[, Sum_prev:= rollapplyr(SalesValue, list(-(1:2)), sum, fill = NA), by=Cust_ID][]
#OR Without chaining 
data_1[, Sum_prev := rollapplyr(SalesValue, list((1:2)), sum, fill = NA), by = Cust_ID][order(Cust_ID,Month)]

      Cust_ID Month StatusCode SalesValue Sum_prev
  1:       1     1          D       1055    NA
  2:       1     2          F        669    NA
  3:       1     3          E        495  1724
  4:       1     4          D        786  1164
  5:       2     1          E        445    NA
  6:       2     2          D        448    NA
  7:       2     3          F        377   893
  8:       2     4          E        173   825
  9:       3     1          F        873    NA
 10:       3     2          E        995    NA
 11:       3     3          D        673  1868
 12:       3     4          F        943  1668

答案 2 :(得分:2)

data.table解决方案:

# sort the data first if the Month column is not ordered for any Cust_ID
data_1 <- data_1[order(Cust_ID, Month)]

# sum up the value of two previous Month for each Cust_ID
data_1[, rsum :=  shift(SalesValue, 1) + shift(SalesValue, 2), by = Cust_ID]

#     Cust_ID Month StatusCode SalesValue rsum
#  1:       1     1          D       1055   NA
#  2:       1     2          F        669   NA
#  3:       1     3          E        495 1724
#  4:       1     4          D        786 1164
#  5:       2     1          E        445   NA
#  6:       2     2          D        448   NA
#  7:       2     3          F        377  893
#  8:       2     4          E        173  825
#  9:       3     1          F        873   NA
# 10:       3     2          E        995   NA
# 11:       3     3          D        673 1868
# 12:       3     4          F        943 1668

答案 3 :(得分:1)

1)sqldf / RpostgreSQL 您可以将窗口函数与PostgreSQL后端一起使用,并且您的代码(经过稍微修改即可工作)在R内是这样的(其中data_1是您的数据帧工作区)。

library(RPostgreSQL)
library(sqldf)

sql <- 'select *, SUM("SalesValue") OVER (PARTITION BY "Cust_ID" 
                       ORDER BY "Month" DESC 
                       ROWS BETWEEN 2 PRECEDING AND 1 PRECEDING ) as PAST_3Y_SALES 
        from "data_1"'

sqldf(sql)

给予:

   Cust_ID Month StatusCode SalesValue past_3y_sales
1        1     4          D        786            NA
2        1     3          E        495           786
3        1     2          F        669          1281
4        1     1          D       1055          1164
5        2     4          E        173            NA
6        2     3          F        377           173
7        2     2          D        448           550
8        2     1          E        445           825
9        3     4          F        943            NA
10       3     3          D        673           943
11       3     2          E        995          1616
12       3     1          F        873          1668

2)data.table / rollapply

或者使用data.table并通过rollapply使用list(-2:-1)将宽度指定为偏移量。

下面的代码已编写为与问题中的SQL代码相对应,但如果您希望每个Cust_ID具有两个NA,而不是一个,则对前几个月的月份求和,其中月份按升序排列(不按指定的降序排列)在问题的SQL中),然后在-Month语句中将Month更改为setorder,并在partial=TRUE中删除rollapply自变量。

library(data.table)
library(zoo)

setorder(data_1, Cust_ID, -Month)
roll <- function(x) rollapply(x, list(-2:-1), sum, partial = TRUE, fill = NA)
data_1[, past_3y_sales := roll(SalesValue), by = Cust_ID]

给予:

> data_1
    Cust_ID Month StatusCode SalesValue past_3y_sales
 1:       1     4          D        786            NA
 2:       1     3          E        495           786
 3:       1     2          F        669          1281
 4:       1     1          D       1055          1164
 5:       2     4          E        173            NA
 6:       2     3          F        377           173
 7:       2     2          D        448           550
 8:       2     1          E        445           825
 9:       3     4          F        943            NA
10:       3     3          D        673           943
11:       3     2          E        995          1616
12:       3     1          F        873          1668

答案 4 :(得分:0)

我遇到了类似的问题,但是上述解决方案并没有帮助我。我的数据是data_1

CIF_ID  LEAD_RESULT 
10000009      1         
10000009      0          
10000025      0         
10000025      0         
10000055      0        

我需要用CIF_ID对LEAD_RESULT求和。

我在library(data.table)内进行了以下操作:

dt <- data.table::as.data.table(data_1)
dt<-dt[, group_sum := sum(LEAD_RESULT), by = "CIF_ID"][]
dt

结果:

CIF_ID  LEAD_RESULT group_sum
10000009       1         1
10000009       0         1
10000025       0         0
10000025       0         0
10000055       0         0