R中按日期计算虚拟变量的累计和

cumulative sum over dummy variable by date in R

我能够在 Python 中完成此数据 ETL 工作。但是,由于我需要与 R 集成并且我是 R 的新手,所以我 post 这里的问题。我想根据 start_date 和 end_date 展开日期,并对从变量 "type"

派生的虚拟变量进行累积频率求和

原始数据有3列,变量名start_date,end_date,类型

start_date, end_date, type
 1/1/2016,  1/3/2016,   A
 1/2/2016,  1/2/2016,   B
 1/2/2016,  1/3/2016,   A

这里是对我试图实现的目标的解释。

对于记录的第一行,类型A出现在从1/1到1/3的每一天(包括开始和结束日期)。

现在第二行,B型只出现在1/2。

到目前为止,1/1有一个'A',1/2有一个'A'和一个'B',1/3有一个'A'。

对其余记录重复此过程。实际上,我在变量 "type"

中有很多这样的行和很多不同的值

基本上,我需要一个高效的算法来对每天变量 "type" 中的所有变量进行频率计数,生成一个以日期作为索引列的数据框以及所有唯一变量中相应的频率计数在变量 "type" 中。希望它澄清。

我需要以下格式的数据框,第一行作为新的 header

 date,      A,       B
 1/1/2016,  1,       0
 1/2/2016,  2,       1
 1/3/2016,  2,       0

@tiffany 的解决方案似乎没有按预期工作。 His/her 嵌套循环代码部分分解为我的以下示例代码。

start_date  end_date    type
1/1/16  1/3/16  A
1/1/16  1/3/16  A
1/1/16  1/8/16  B
1/1/16  1/14/16 B
1/5/16  1/19/16 B
1/7/16  1/13/16 C
1/9/16  1/18/16 A
1/13/16 1/19/16 D
1/13/16 1/19/16 A
1/14/16 1/22/16 B
1/15/16 1/29/16 B
1/16/16 1/22/16 D

正确的部分是:

results <- data.frame(date = dates)

for(t in unique(df$type)) {
  for(d in dates) {
    results[results$date == d, t] <- 
      length(df[df$start_date <= d & df$end_date >= d & df$type == t],'type')
  }
}

提前感谢您的帮助。为了表明我本着 stackover 流社区的精神并不懒惰,这是我写的 Python 版本:

import pandas as pd

df = pd.read_csv("dates.csv")

factor_type = list(df['type'].unique())

columns = ['date']
columns.extend(factor_type)


result = []

dates_dict = {}
i = 0


for index,row in df.iterrows():
    start_end = pd.date_range(row['start_date'], row['end_date'])
    factor = row['variable_type']
    factor_index = factor_type.index(factor)
    for x in start_end:
        date_obj = x.date()
        date_str = '%s/%s/%s' % (date_obj.month, date_obj.day,date_obj.year)
        if date_str in dates_dict:
            row_index = dates_dict[date_str]
            result[row_index+1][factor_index+1]+=1
        else:
            dummy_row = [0]*len(factor_type)
            dummy_row[factor_index]=1
            result.append([date_str]+dummy_row)
            dates_dict[date_str]=i+1


result_df = pd.DataFrame(result,columns=columns)  

我不确定我是否完全理解你在找什么(你说 "cumulative sum" 但我认为你真的想计算未清项的数量。)

如果是这样,这里有一些(相对肮脏的)代码可以为您提供您想要的东西,适用于最早 start_date 和最晚 end_date 之间的每个日期。

library(lubridate)
start_date <- c("1/1/2016", "1/2/2016", "1/2/2016")
end_date <- c("1/3/2016", "1/2/2016", "1/3/2016")
type <- c("A", "B", "A")

将字符串转换为日期,使接下来的操作更容易。

df <- data.frame(start_date, end_date, type)
df$start_date <- as.Date(mdy(df$start_date))
df$end_date <- as.Date(mdy(df$end_date))

制作最早 start_date 和最晚 end_date 之间的日期矢量。

dates <- seq(from = min(c(df$start_date, df$end_date)),
             to = max(c(df$start_date, df$end_date)),
             by = 1)

以您想要的格式获取数据:

results <- data.frame(date = dates, openA = NA, openB = NA)
for(d in dates) {
  results$openA[results$date == d] <- 
    length(df[df$start_date <= d & df$end_date >= d & df$type == "A"])

  results$openB[results$date == d] <- 
    length(df[df$start_date <= d & df$end_date >= d & df$type == "B"])
}

对于任意数量的类型,您可以这样做:

results <- data.frame(date = dates)

for(t in unique(df$type)) {
  for(d in dates) {
    results[results$date == d, t] <- 
      length(df[df$start_date <= d & df$end_date >= d & df$type == t])
  }
}

我想提供一个dplyr-solution。

首先,我愉快地借用了tiffany的工作来构建dataframe df。然后

  • 列出从开始到结束的日期

     df2<-df%>%
             rowwise()%>%
             mutate(dates = list(as_date(start_date:end_date)))
    
  • 列出所有这些日期,附上正确的类型,然后按日期和总和分组

      df3<-bind_rows(apply(df2,1,function(x){
                       data.frame(Date = unlist(x$dates))%>%mutate(type=x$type[1])
         }))%>%
          group_by(Date)%>%
          summarise(A = sum(type=="A"),
                    B = sum(type=="B"))
    

这里有两种使用数据的方式table- 一种高效但更难阅读,第二种效率较低但更易于阅读。

首先,将两列都转换为正确的日期 类(我正在使用 data.tables as.IDate 函数来表示内部整数,而不是数字)

library(data.table) 
cols <- c("start_date", "end_date")
setDT(df)[, (cols) := lapply(.SD, as.IDate, format = "%m/%d/%Y"), .SDcols = cols]

效率较低的解决方案

一个简单(但效率不高)的方法是按行展开日期(已经提供),然后做一个简单的dcast,这既非常有效又不关心有多少级别你有 type

res <- df[, .(Date = seq.int(start_date, end_date, 1L), type), by = 1:nrow(df)]
dcast(res, Date ~ type, length)
# Using 'type' as value column. Use 'value.var' to override
#          Date A B
# 1: 2016-01-01 1 0
# 2: 2016-01-02 2 1
# 3: 2016-01-03 2 0

更高效的解决方案

此解决方案不涉及按行操作,而是使用 foverlaps 函数对整个日期范围进行操作。第一步(同样,就像已经提供的那样)是创建一个整体范围,将其设置为开始和结束范围,并设置一个键(用于进一步操作)

Intervals <- data.table(start_date = df[, seq.int(min(start_date), max(end_date), 1L)]) # overall range
Intervals[, end_date := start_date] # set start/end ranges as same values
setkey(Intervals, start_date, end_date) # key

现在剩下的就是 运行 foverlaps 并再次使用 dcast 转换为宽格式

dcast(foverlaps(df, Intervals), start_date ~ type, length)
# Using 'type' as value column. Use 'value.var' to override
#    start_date A B
# 1: 2016-01-01 1 0
# 2: 2016-01-02 2 1
# 3: 2016-01-03 2 0