微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

在R中计算两次之间的营业时间

如何解决在R中计算两次之间的营业时间

我正在尝试计算两次(消息创建日期和消息打开日期)之间的小时数。我已使用以下代码创建函数

biz_hrs <- Vectorize(function(CreateDate,OpenedDate,starting_time = '8:00',ending_time = '17:00',holidays = NULL){
if(OpenedDate < CreateDate){
    
    return(NA)
    
  } else {
    
    start_datetime <- as.POSIXct(paste0(substr(start,1,11),starting_time,':00'))
    end_datetime <- as.POSIXct(paste0(substr(end,ending_time,':00'))
    
    if(as.Date(CreateDate) == as.Date(OpenedDate) & !as.Date(CreateDate) %in% holidays & !format(as.Date(CreateDate),"%u") %in% c(6,7)){ #if starting time stamp is on same day as ending time stamp and if day is not a holiday or weekend
      
      if(CreateDate > start_datetime & OpenedDate < end_datetime){ #if starting time stamp is later than start business hour and ending time stamp is earlier then ending business hour.
        return(as.numeric(difftime(OpenedDate,CreateDate),units = 'hours'))
      } else if(CreateDate > start_datetime & OpenedDate > end_datetime & CreateDate < end_datetime){ #if starting time stamp is later than end business hour and ending time stamp is earlier then ending business hour.
        return(as.numeric(difftime(as.POSIXct(paste0(substr(start,':00')),units = 'hours'))
      } else if(CreateDate < start_datetime & OpenedDate < end_datetime & OpenedDate > start_datetime){ #if starting time stamp is earlier than end business hour and ending time stamp is later than starting business hour.
        return(as.numeric(difftime(OpenedDate,start_datetime),units = 'hours'))
      } else if(CreateDate > end_datetime & OpenedDate > end_datetime){ #if starting time stamp is later than end business hour and ending time stamp is later than ending business hour.
        return(0)
      } else if(CreateDate < start_datetime & OpenedDate < start_datetime){ #if starting time stamp is earlier than start business hour and ending time stamp is earlier than starting business hour.
        return(0)
      } else {
        return(as.numeric(difftime(end_datetime,units = 'hours'))
      }
      
    } else { #if starting time stamp and ending time stamp occured on a different day.
      
      business_hrs <- as.numeric(difftime(as.POSIXct(paste0('2017-01-01',as.POSIXct(paste0('2017-01-01',':00')) #calculate business hours range by specified parameters
      ),units = 'hours')
      
      start_day_hrs <- ifelse(CreateDate < as.POSIXct(paste0(substr(start,':00')) & !as.Date(CreateDate) %in% holidays & !format(as.Date(CreateDate),7),#if start time stamp is earlier than specified ending time
                              as.numeric(difftime(as.POSIXct(paste0(substr(start,units = 'hours'),#calculate time between time stamp and specified ending time
                              0 #else set zero
      ) #calculate amount of time on starting day
      start_day_hrs <- pmin(start_day_hrs,business_hrs) #cap the maximum amount of hours dertermined by the specified business hours
      start_day_hrs
      end_day_hrs <- ifelse(OpenedDate > as.POSIXct(paste0(substr(end,':00')) & !as.Date(OpenedDate) %in% holidays & !format(as.Date(OpenedDate),#if end time stamp is later than specified starting time
                            as.numeric(difftime(end,as.POSIXct(paste0(substr(end,':00'))),#calculate time between time stamp and specified starting time
                            0) #calculate amount of time on ending day
      end_day_hrs <- pmin(end_day_hrs,business_hrs) #cap the maximum amount of hours dertermined by the specified business hours
      days_between <- seq(as.Date(CreateDate),as.Date(OpenedDate),by = 1) #create a vector of dates (from and up to including) the starting time stamp and ending time stamp
      business_days <- days_between[!days_between %in% c(as.Date(CreateDate),as.Date(OpenedDate)) & !days_between %in% holidays & !format(as.Date(days_between),7)] #remove weekends and holidays from vector of dates
      
      return(as.numeric(((length(business_days) * business_hrs) + start_day_hrs + end_day_hrs))) #multiply the remaining number of days in the vector (business days) by the amount of business hours and add hours from the starting and end day. Return the result
      
    }
    
  }
  
  
})

然后输入我的数据

Weekly_Final$ResponseTime <- biz_hrs(Weekly_Final$CreateDate,Weekly_Final$OpenedDate,'8:00','17:00')

但是出现以下错误

Error in as.character(x) : 
  cannot coerce type 'closure' to vector of type 'character' 

解决方法

重命名变量时,您有时会犯错。调用substr(start,1,11)时出现错误,因为没有名为start的变量。 R认为您是指称为start功能。我假设CreateDate曾经被称为start,而OpenedDate曾经被称为end。如果我们只是更新这些,则该函数将返回输出

biz_hrs <- Vectorize(function(CreateDate,OpenedDate,starting_time = '8:00',ending_time = '17:00',holidays = NULL){
if(OpenedDate < CreateDate){
    
    return(NA)
    
  } else {
    
    start_datetime <- as.POSIXct(paste0(substr(CreateDate,11),starting_time,':00'))
    end_datetime <- as.POSIXct(paste0(substr(OpenedDate,ending_time,':00'))
    
    if(as.Date(CreateDate) == as.Date(OpenedDate) & !as.Date(CreateDate) %in% holidays & !format(as.Date(CreateDate),"%u") %in% c(6,7)){ #if starting time stamp is on same day as ending time stamp and if day is not a holiday or weekend
      
      if(CreateDate > start_datetime & OpenedDate < end_datetime){ #if starting time stamp is later than start business hour and ending time stamp is earlier then ending business hour.
        return(as.numeric(difftime(OpenedDate,CreateDate),units = 'hours'))
      } else if(CreateDate > start_datetime & OpenedDate > end_datetime & CreateDate < end_datetime){ #if starting time stamp is later than end business hour and ending time stamp is earlier then ending business hour.
        return(as.numeric(difftime(as.POSIXct(paste0(substr(start,':00')),units = 'hours'))
      } else if(CreateDate < start_datetime & OpenedDate < end_datetime & OpenedDate > start_datetime){ #if starting time stamp is earlier than end business hour and ending time stamp is later than starting business hour.
        return(as.numeric(difftime(OpenedDate,start_datetime),units = 'hours'))
      } else if(CreateDate > end_datetime & OpenedDate > end_datetime){ #if starting time stamp is later than end business hour and ending time stamp is later than ending business hour.
        return(0)
      } else if(CreateDate < start_datetime & OpenedDate < start_datetime){ #if starting time stamp is earlier than start business hour and ending time stamp is earlier than starting business hour.
        return(0)
      } else {
        return(as.numeric(difftime(end_datetime,units = 'hours'))
      }
      
    } else { #if starting time stamp and ending time stamp occured on a different day.
      
      business_hrs <- as.numeric(difftime(as.POSIXct(paste0('2017-01-01',as.POSIXct(paste0('2017-01-01',':00')) #calculate business hours range by specified parameters
      ),units = 'hours')
      
      start_day_hrs <- ifelse(CreateDate < as.POSIXct(paste0(substr(CreateDate,':00')) & !as.Date(CreateDate) %in% holidays & !format(as.Date(CreateDate),7),#if start time stamp is earlier than specified ending time
                              as.numeric(difftime(as.POSIXct(paste0(substr(CreateDate,units = 'hours'),#calculate time between time stamp and specified ending time
                              0 #else set zero
      ) #calculate amount of time on starting day
      start_day_hrs <- pmin(start_day_hrs,business_hrs) #cap the maximum amount of hours dertermined by the specified business hours
      start_day_hrs
      end_day_hrs <- ifelse(OpenedDate > as.POSIXct(paste0(substr(OpenedDate,':00')) & !as.Date(OpenedDate) %in% holidays & !format(as.Date(OpenedDate),#if end time stamp is later than specified starting time
                            as.numeric(difftime(end,as.POSIXct(paste0(substr(OpenedDate,':00'))),#calculate time between time stamp and specified starting time
                            0) #calculate amount of time on ending day
      end_day_hrs <- pmin(end_day_hrs,business_hrs) #cap the maximum amount of hours dertermined by the specified business hours
      days_between <- seq(as.Date(CreateDate),as.Date(OpenedDate),by = 1) #create a vector of dates (from and up to including) the starting time stamp and ending time stamp
      business_days <- days_between[!days_between %in% c(as.Date(CreateDate),as.Date(OpenedDate)) & !days_between %in% holidays & !format(as.Date(days_between),7)] #remove weekends and holidays from vector of dates
      
      return(as.numeric(((length(business_days) * business_hrs) + start_day_hrs + end_day_hrs))) #multiply the remaining number of days in the vector (business days) by the amount of business hours and add hours from the starting and end day. Return the result
      
    }
    
  }
  
})

因此:

biz_hrs("2001-05-07","2001-05-08")
#> 2001-05-07 
#>          9 

我不知道这是否符合您的期望。

顺便说一句,您应该签出lubridate软件包,这将使您简化和缩短代码。这将使调试更加容易。

我认为以下简单功能可以很好地代替biz_hrs并保留界面

library(lubridate)

biz_hrs <- function(CreateDate,start_time = '8:00',end_time = '17:00',holidays = NULL)
{
    begin    <- as.Date(CreateDate)
    end      <- as.Date(OpenedDate)
    hours    <- as.numeric(strsplit(end_time,":")[[1]][1]) -
                as.numeric(strsplit(start_time,":")[[1]][1])
    
    sapply(seq_along(begin),function(i) {
      if(begin[i] > end[i]) NA
      else {
        all_days <- seq(begin[i],end[i],"1 day")
        sum(hours * (wday(all_days) %in% 2:6 & is.na(match(all_days,holidays))))
      }})
}
,

建立在艾伦的建议之上。该功能非常笨拙。我们可以结合使用lubridatebizdays软件包来对函数进行矢量化处理。问题实际上是找到两个日期之间的工作日数,并根据营业时间和营业时间进行调整。首先,我们必须提前几天,以确保它们在营业时间和营业时间之内。
使用lubridate的hms(时分秒)类有点摆弄,我们可以创建一个移动日期的函数。

library(lubridate)
library(bizdays)
cal <- create.calendar('mycal',weekdays = c('saturday','sunday'))
open <- hms('08:00:00')
close <- hms('17:00:00')
start <- as_datetime('2017-05-10 18:00:00') # After closing time,so real date = '2017-05-11 08:00:00'
end <- as_datetime('2017-05-11 12:00:00') # 4 hours after open

fix_biz_date <- function(date,open,close){
  if(!is.POSIXct(date))
    date <- lubridate::as_datetime(date)
  time <- hms(strftime(date,'%H:%M:%S'))  
  # Fix dates
  ind <- which(time >= close)
  if(length(ind) > 0)
    date[ind] <- date[ind] + days(1)
  # Fix times
  ind <- c(ind,which(time < open))
  if(length(ind) > 0){
    hour(date[ind]) <- hour(open)
    minute(date[ind]) <- minute(open)
    second(date[ind]) <- second(open)
  }
  date
}
fix_biz_date(start,close)
[1] "2017-05-11 08:00:00 UTC"
fix_biz_date(end,close)
[1] "2017-05-11 12:00:00 UTC"

现在我们已经适当地更改了日期,我们只需要一个函数来查找两者之间的工作日和工作时间。

#' @param start Starting date-time
#' @param end Ending date-time
#' @param cal calendar object (bizdays::create.calendar)
#' @param open hm(s) second object (lubridate::hms),specifying opening time of day
#' @param open hm(s) second object (lubridate::hms),specifying closing time of day
wh_diff <- function(start,end,cal,close){
  if(length(start) != length(end))
    stop('start and end needs to be of equal length!')
  if(!is.period(open))
    open <- hms(open)
  if(!is.period(close))
    close <- hms(close)
  start <- fix_biz_date(start,close)
  end <- fix_biz_date(end,close)
  sTime <- strftime(start,'%H:%M:%S')
  eTime <- strftime(end,'%H:%M:%S')
  days_dif <- bizdays(start,cal) 
  days_dif * (as.numeric(close - open) / 3600) + 
    as.numeric(hms(eTime) - hms(sTime)) / 3600
}
wh_diff(start,close) # Expected 4 hours.
[1] 4

现在,此功能实际上已被适当地向量化,并且将采用等量的开始日期和结束日期,或者采用多个结束日期的单个开始(反之亦然)。

end <- offset(end,seq(0,180,length.out = 91),cal)
wh_diff(start,close) # something like.. seq(0,18 * 91,length.out = 91) 

start <- offset(start,0:90,close) # Expect seq(9,9 * 91,length.out = 91)

此方法将比使用vectorize快很多时间,后者将在幕后创建几个for-loops来完成工作。但是,它确实需要一些想象力和一些处理日期的经验。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。