【问题标题】:Combine datasets by date range and categorical variable按日期范围和分类变量组合数据集
【发布时间】:2016-12-04 07:35:56
【问题描述】:

假设我有两个数据集。一个包含带有开始/结束日期的促销列表,另一个包含每个程序的月度销售数据。

promotions = data.frame(
    start.date = as.Date(c("2012-01-01", "2012-06-14", "2012-02-01", "2012-03-31", "2012-07-13")), 
    end.date = as.Date(c("2014-04-05", "2014-11-13", "2014-02-25", "2014-08-02", "2014-09-30")), 
    program = c("a", "a", "a", "b", "b"))

sales = data.frame(
    year.month.day = as.Date(c("2013-02-01", "2014-09-01", "2013-08-01", "2013-04-01", "2012-11-01")), 
    program = c("a", "b", "a", "a", "b"), 
    monthly.sales = c(200, 200, 200, 400, 200))

请注意,sales$year.month.day 用于表示年/月。包含日期,因此 R 可以更简单地将列视为日期对象的向量,但它与实际销售额无关。

我需要确定每个计划每月发生的促销次数。这是一个产生我想要的输出的循环示例:

sales$count = rep(0, nrow(sales))
sub = list()
for (i in 1:nrow(sales)) {
  sub[[i]] = promotions[which(promotions$program == sales$program[i]),]
  if (nrow(sub[[i]]) > 1) {
    for (j in 1:nrow(sub[[i]])) {
      if (sales$year.month.day[i] %in% seq(from = as.Date(sub[[i]]$start.date[j]), to = as.Date(sub[[i]]$end.date[j]), by = "day")) {
        sales$count[i] = sales$count[i] + 1
      }
    }
  }
}

示例输出:

 sales = data.frame(
    year.month.day = as.Date(c("2013-02-01", "2014-09-01", "2013-08-01", "2013-04-01", "2012-11-01")), 
    program = c("a", "b", "a", "a", "b"), 
    monthly.sales = c(200, 200, 200, 400, 200),
    count = c(3, 1, 3, 3, 2)
)

但是,由于我的实际数据集非常大,所以当我在 R 中运行时,这个循环会崩溃。

有没有更有效的方法来达到同样的效果?也许与dplyr有关?

【问题讨论】:

  • 能否添加所需的输出数据框?我不太了解您的循环的输出。此外,如果您对每个程序每月的促销次数感兴趣,为什么需要销售数据框?
  • 我已经编辑了帖子以包含我的循环的输出。该循环将“计数”列添加到原始销售 data.frame。
  • 对于我的分析,我需要每个程序的销售额和每月促销次数,所以是的,销售额 data.frame 是必要的。

标签: r performance for-loop dplyr


【解决方案1】:

使用当前开发版本的data.table新实现的non-equi连接:

require(data.table) # v1.9.7+
setDT(promotions) # convert to data.table by reference
setDT(sales)

ans = promotions[sales, .(monthly.sales, .N), by=.EACHI, allow.cartesian=TRUE, 
        on=.(program, start.date<=year.month.day, end.date>=year.month.day), nomatch=0L]

ans[, end.date := NULL]
setnames(ans, "start.date", "year.month.date")
#    program year.month.date monthly.sales N
# 1:       a      2013-02-01           200 3
# 2:       b      2014-09-01           200 1
# 3:       a      2013-08-01           200 3
# 4:       a      2013-04-01           400 3
# 5:       b      2012-11-01           200 2

查看开发版安装说明here

【讨论】:

    【解决方案2】:

    你可以用 sql 做到这一点。

    library(sqldf)
    sqldf("select s.ymd,p.program,s.monthlysales, count(*) from promotions p outer left join sales s on p.program=s.program 
    where s.ymd between p.startdate and p.enddate and p.program=s.program group by s.ymd, s.program" )
    

    这将首先加入 2 个数据集,其中 ymd in sales 介于促销的开始日期和结束日期之间,并且两个数据中的程序相同。然后它将按 ymd 分组并计算实例。我已经从变量名称中删除了句点。

    【讨论】:

    • 这似乎每个 ymd 只返回一行。对于某些数据集可能没问题,但对于每个分类变量需要每月计数的数据集则不然
    • 我已经对其进行了编辑,这样它就可以为您提供与程序一样多的 ymd。
    【解决方案3】:

    我是 Hadley 包的粉丝:

    library(dplyr)
    library(lubridate)
    

    楼层日期,因此它们的格式与 sales 数据框相同:

    df <- promotions %>% 
        mutate(start.date = floor_date(start.date, unit = "month"),
               end.date = floor_date(end.date, unit = "month"))
    

    扩展日期间隔:

    df$output <- mapply(function(x,y) seq(x, y, by =  "month"),
           df$start.date,
           df$end.date)
    

    根据日期范围、分组和计数扩展数据框,并合并到日期和程序的销售:

    df %>% tidyr::unnest(output) %>% 
        group_by(output, program) %>%
        summarise(prom_num = n()) %>%
        merge(sales, ., 
          by.x = c("year.month.day", "program"),
          by.y = c("output", "program"))
    

    输出:

      year.month.day program monthly.sales prom_num
    1     2012-11-01       b           200        2
    2     2013-02-01       a           200        3
    3     2013-04-01       a           400        3
    4     2013-08-01       a           200        3
    5     2014-09-01       b           200        1
    

    【讨论】:

      【解决方案4】:

      可以试试?data.table::foverlaps

      library(data.table)
      setDT(sales)[, c("start.date", "end.date") := year.month.day] # Add overlap cols
      setkey(sales, program, start.date, end.date) # Key for join
      res <- foverlaps(setDT(promotions), sales)[, .N, by = year.month.day] # Count joins
      sales[res, count := i.N, on = "year.month.day"] # Update `sales` with results
      sales
      #    year.month.day program monthly.sales start.date   end.date count
      # 1:     2013-02-01       a           200 2013-02-01 2013-02-01     3
      # 2:     2013-04-01       a           400 2013-04-01 2013-04-01     3
      # 3:     2013-08-01       a           200 2013-08-01 2013-08-01     3
      # 4:     2012-11-01       b           200 2012-11-01 2012-11-01     2
      # 5:     2014-09-01       b           200 2014-09-01 2014-09-01     1
      

      这基本上是在sales 中创建间隔列,由它们连接 + 由program,计算重叠,并连接回sales。如果它真的困扰您,您可以通过执行 sales[, c("start.date", "end.date") := NULL] 删除其他列。谷歌foverlapsdata.table 获取更多示例

      【讨论】:

        猜你喜欢
        • 2021-06-09
        • 1970-01-01
        • 2018-03-13
        • 1970-01-01
        • 2013-11-04
        • 2021-11-16
        • 2021-03-05
        相关资源
        最近更新 更多