R/as.annual.R

Defines functions shiftyear as.OctMar as.seasons leapdate as.4seasons.dsensemble as.4seasons.field as.4seasons.spell as.4seasons.station as.4seasons.day as.4seasons.default as.4seasons as.daily.field as.daily.default yyyymmdd as.daily as.monthly.field as.monthly.default yyyymm as.monthly as.annual.spell as.annual.station as.annual.yearqtr as.annual.integer as.annual.numeric as.annual.default as.annual annual.eof annual.field annual.dsensemble annual.spell annual.station annual.default annual.zoo annual

Documented in annual annual.default annual.dsensemble annual.eof annual.field annual.spell annual.station annual.zoo as.4seasons as.4seasons.day as.4seasons.default as.4seasons.dsensemble as.4seasons.field as.4seasons.spell as.4seasons.station as.annual as.annual.default as.annual.integer as.annual.numeric as.annual.spell as.annual.station as.annual.yearqtr as.daily as.monthly as.monthly.default as.monthly.field as.OctMar as.seasons

#' Conversion to esd objects.
#' 
#' \code{as.annual} and \code{annual} aggregates time series into annual values (e.g. means).
#'
#' \code{as.monthly} aggregates time series into monthly values (e.g. means).
#'
#' \code{as.daily} aggregates time series into daily values (e.g. means).
#'
#' \code{as.4seasons} aggregates to four seasons ('djf': December-February, 'mam': March-May, 'jja': June-August, 'son': September-November)
#'
#' \code{as.seasons} aggregates to a user defined season with input arguments 'start' and 'end' giving the dates. To select march to september, use either start='03-01' and end='09-30' or start = 3 and end = 9.
#'
#' \code{as.OctMar} aggregates to the season October to March, which is the rainy season in parts of Africa. 
#'
#' @aliases as.annual as.annual.default as.annual.numeric as.annual.integer as.annual.yearqtr as.annual.station as.annual.spell
#' annual annual.zoo annual.default annual.dsensemble annual.station annual.spell annual.field annual.eof
#' as.monthly as.monthly.default as.monthly.station as.monthly.field
#' as.4seasons as.4seasons.default as.4seasons.day as.4seasons.station as.4seasons.spell as.4seasons.field as.4seasons.dsensemble as.seasons as.daily as.OctMar
#'
#' @seealso aggregate
#' 
#' @param x an input object of, e.g., class 'station', 'field'
#' @param FUN a function, see \code{\link{aggregate.zoo}}
#' @param nmin Minimum number of data points (e.g. days or months) with valid
#' data accepted for annual estimate. NULL demands complete years.
#' @param start makes it possible to estimate annual aggregated statistics that start into the year. 
#' E.g. start='Sep' starts the year on September 1st. Other allowed formats are start='MM-DD'. 
#' @param format 'numeric' or 'character'
#' @param na.rm a boolean; if TRUE, ignore NA - see see \code{\link{mean}}
#' @param verbose a boolean; if TRUE print information about progress
#' @param \dots additional argument
#' 
#' @return Same class as x
#'
#' @keywords utilities
#'
#' @examples
#' data(ferder)
#' plot(annual(ferder,FUN="min"))
#' plot(annual(ferder,FUN="IQR",na.rm=TRUE))
#' plot(annual(ferder))
#' lines(annual(ferder,start='Jul'))
#' 
#' data(bjornholt)
#' plot(as.4seasons(bjornholt,threshold=1,FUN="exceedance"))
#' 
#' @export annual
annual <- function(x, ...) UseMethod("annual")

#' @exportS3Method
#' @export annual.zoo
annual.zoo <- function(x,FUN='mean',na.rm=TRUE,nmin=NULL, start = NULL, verbose=FALSE,...) {
  if (verbose) print("annual.zoo")
  if (inherits(x,c('annual','year'))) return(x)
  attr(x,'names') <- NULL
  #  yr <- year(x)  REB: 08.09.2014
  class(x) <- 'zoo'
  ## If start specified, then use lag to make the series start to estimate annual aggregate
  ## starting from any random day in the year
  if (!is.null(start)) x <- shiftyear(x,start,verbose)
  ## Update the units for annual sums:
  if (FUN=='sum') {
    attr(x,'unit') <- sub('day','year',attr(x,'unit'))
    attr(x,'unit') <- sub('month','year',attr(x,'unit'))
    attr(x,'unit') <- sub('season','year',attr(x,'unit'))
  }
  
  #  y <- aggregate(x,yr,FUN=match.fun(FUN),...,na.rm=na.rm)
  if ( (sum(is.element(names(formals(FUN)),'na.rm')==1)) |
       (sum(is.element(FUN,c('mean','min','max','sum','quantile')))>0) )
    #    y <- aggregate(x,yr,FUN=FUN,...,na.rm=na.rm) else
    #    y <- aggregate(x,yr,FUN=FUN,...)
    y <- aggregate(x,year,FUN=FUN,...,na.rm=na.rm)
  else
    y <- aggregate(x,year,FUN=FUN,...)
  ## replace infinite values by NA
  y[which(is.infinite(y))] <- NA
  ## Check minimum valid data points:
  d <- dim(y)
  if (!is.null(nmin)) {
    ## Need to account for both multiple and single series
    if (verbose) print(paste('nmin=',nmin))
    nok <- coredata(aggregate(x,year,FUN='nv'))
    ycd <- coredata(y) 
    ycd[nok < nmin] <-  NA
    ## If multivariate/matrix: reset dimensions
    if (!is.null(d)) dim(ycd) <- d
    coredata(y) <- ycd
  }
  attr(y,'dimnames') <- NULL
  invisible(y)
}


#' @exportS3Method
#' @export
annual.default <- function(x,FUN='mean',na.rm=TRUE, nmin=NULL,start=NULL,...,
                           threshold=NULL,regular=NULL,frequency=NULL,
                           verbose=FALSE) { ## 
  
  if (verbose) print(paste('annual.default',FUN))
  
  ## Case when subsetting one specific season / in this case nmin =1
  
  ## If already annual, then return
  if (inherits(x,'annual')) return(x)
  ## If start specified, then use lag to make the series start to estimate annual aggregate
  ## starting from any random day in the year
  if (!is.null(start)) x <- shiftyear(x,start,verbose)
  ## Update the units for annual sums:
  if (FUN=='sum') {
    attr(x,'unit') <- sub('day','year',attr(x,'unit'))
    attr(x,'unit') <- sub('month','year',attr(x,'unit'))
    attr(x,'unit') <- sub('season','year',attr(x,'unit'))
  }
  
  ## This line to make the function more robust.
  if (length(grep('nmin',ls()))==0) nmin <- NULL
  
  if (inherits(FUN,'function')) FUN <- deparse(substitute(FUN)) # REB110314
  attr(x,'names') <- NULL
  yr <- year(x)
  nmo <- length(levels(factor(month(x))))
  d <- dim(x)
  if(is.null(d)) d <- c(length(x),1)
  #print(table(yr))
  
  YR <- as.numeric(rownames(table(yr)))
  nyr <- as.numeric(table(yr))
  
  # Need to accomodate for the possibility of more than one station series.
  if (inherits(x,'day')) {
    if (is.null(nmin)) nmin <- 30*nmo
  } else if (inherits(x,'month')) {
    if (is.null(nmin)) nmin <- 12
  } else if (inherits(x,'season')) {
    if (is.null(nmin)) nmin <-  length(levels(factor(month(x))))
  } else {
    nmin <- NA
  }
  if (verbose) {print(paste('nmin=',nmin)); print(class(x))}
  
  ## Convert x to a zoo-object:
  if (verbose) print('Number of valid data points')
  X <- zoo(coredata(x),order.by=index(x))
  attr(X,'units') <- esd::unit(x)
  attr(X,'variable') <- varid(x)
  
  ## Check how many valid data points)
  nok <- aggregate(X,year,FUN='nv')
  
  if (FUN == 'sum') na.rm <- FALSE ## AM
  if (verbose) print(paste('aggregate: FUN=',FUN))
  
  if (verbose) str(X)
  
  if (sum(is.element(names(formals(FUN)),'threshold')==1)) {
    ## If threshold needed - set a default:
    if (is.null(threshold) & inherits(x,'station')) {
      threshold <- 1 ## AM added 20-05-2015
      if (verbose) print('Warning : threshold value not found and set to 1')
    }
    y <- aggregate(X,year,FUN=FUN,...,threshold=threshold) ## AM 20-05-2015
  } else if ((sum(is.element(names(formals(FUN)),'na.rm')==1)) |
             (sum(is.element(FUN,c('mean','min','max','sum','quantile')))>0)) {
    if (verbose) print('Function has na.rm-argument')
    y <- aggregate(X,year,FUN=FUN,...,na.rm=na.rm)
  } else {
    if (verbose) print('Function has no treshold nor na.rm arguments')
    y <- aggregate(X,year,FUN=FUN,...) # REB
  }
  y[!is.finite(y)] <- NA ## AM
  
  if (verbose) print('check for incomplete sampling')
  ## Flag the data with incomplete sampling as NA
  if (!is.na(nmin)) {
    ## Need to account for both multiple and single series
    if (verbose) {print(paste('nmin=',nmin)); print(nok)}
    #nok <- coredata(aggregate(x,year,FUN='nv'))
    ycd <- coredata(y) 
    ycd[coredata(nok) < nmin] <-  NA
    ## If multivariate/matrix: reset dimensions
    if (!is.null(dim(x))) dim(ycd) <- dim(y)
    y <- zoo(ycd,order.by=index(y))
    if (verbose) print(paste('mask',sum(nok < nmin),'years with nv <',nmin))
  }
  ## Check if the series is gappy:
  if (verbose) print('Fill in gaps')
  it <- seq(min(index(y)),max(index(y)),by=1)
  if (verbose) print(c(range(it),length(it)))
  if(is.null(dim(y))) {
    z <- zoo(rep(NA,length(it)),order.by=it)
    z[is.element(it,index(y))] <- y
  } else {
    z <- zoo(matrix(rep(NA,ncol(y)*length(it)),
                    ncol=ncol(y), nrow=length(it)),
             order.by=it)
    z[is.element(it,index(y)),] <- y
  }
  y <- z; rm('z')
  
  ## Copy the old attributes and reset as the original class:
  y <- attrcp(x,y,ignore="names")
  args <- list(...)
  if (verbose) print(names(args))
  
  ## Set appropriate units and variable names:
  if (verbose) print('Set appropriate units and variable names')
  if (FUN=="count")  {
    if (verbose) print("Count")
    attr(y,'unit') <-
      rep(paste("counts | X >",threshold," * ",attr(x,'unit')),d[2])
  } else if (FUN=="freq") {
    if (verbose) print("Frequency")
    attr(y,'variable') <- rep('f',d[2])
    attr(y,'unit') <- rep('fraction',d[2])
    #    attr(y,'unit') <- rep(paste("frequency | X >",threshold," * ",attr(x,'unit')),d[2])
  } else if (FUN=="wetfreq") {
    if (verbose) print("Wet-day frequency")
    attr(y,'variable') <- rep('f[w]',d[2])
    attr(y,'unit') <- rep('fraction',d[2])
    attr(y,'longname')[] <- 'Wet-day frequency'
    #    attr(y,'unit') <- rep(paste("frequency | X >",threshold," * ",attr(x,'unit')),d[2])
  } else if (FUN=="wetmean") {
    if (verbose) print("Wet-day mean")
    attr(y,'variable') <- rep('mu',d[2])
    attr(y,'longname')[] <- 'Wet-day mean precipitation'
    attr(y,'unit') <- rep('mm/day',d[2])
    #    n <- count(X,threshold=threshold) # REB
    #    n <- aggregate(X,year,FUN='count', threshold=threshold,...,
    #                   regular = regular, frequency = frequency)
    n <- nok  # Not the count above threshold, but number of valid data points
    bad <- n<nmin
    #bad <- coredata(n)==0
    #coredata(n)[bad] <- 1
    std.err <- try(2*coredata(y)/sqrt(coredata(n)-1))
    if (!inherits(std.err,'try-error')) { 
      std.err[bad] <- NA
      attributes(std.err) <- NULL
      dim(std.err) <- dim(y)
      attr(y,'standard.error') <- zoo(std.err,order.by=index(y))
    }
  } else if (FUN=="mean") {
    if (verbose) print("mean")
    sigma <- aggregate(X, year, FUN='sd', ...,
                       regular = regular, frequency = frequency)
    ## KMP 2022-12-09: threshold is not set by default
    ## n should be the numnber of valid data points, not count above threshold
    n <- nok
    bad <- n<nmin
    #    n <- count(x,threshold=threshold)
    #n <- aggregate(X,year,FUN='count', threshold=threshold,...,
    #               regular = regular, frequency = frequency)
    #bad <- coredata(n)==0
    #coredata(n)[bad] <- 1
    std.err <- try(2*coredata(sigma)/sqrt(coredata(n)-1))
    if (!inherits(std.err,'try-error')) { 
      std.err[bad] <- NA
      attributes(std.err) <- NULL
      dim(std.err) <- dim(sigma)
    }
    attr(y,'standard.error') <- zoo(std.err,order.by=index(sigma))
  } else if (FUN=="HDD") {
    attr(y,'variable') <- rep('HDD',d[2])
    attr(y,'unit') <- rep('degree-days',d[2])
  } else if (FUN=="CDD") {
    attr(y,'variable') <- rep('CDD',d[2])
    attr(y,'unit') <- rep('degree-days',d[2])
  } else if (FUN=="GDD") {
    attr(y,'variable') <- rep('GDD',d[2])
    attr(y,'unit') <- rep('degree-days',d[2])
  } else attr(y,'unit') <- attr(x,'unit')
  
  attr(y,'history') <- history.stamp(x)
  class(y) <- class(x)
  class(y)[length(class(y))-1] <- "annual"
  if (class(y)[1]=="spell") class(y) <- class(y)[-1]
  #print(class(y)); print(class(x))
  invisible(y)
}

#' @exportS3Method
#' @export
annual.station <- function(x,FUN='mean',nmin=NULL,start=NULL,threshold=NULL,verbose=FALSE,...) {
  if (verbose) print(paste('annual.station',FUN))
  attr(x,'names') <- NULL
  y <- annual.default(x,FUN=FUN,nmin=nmin,start=start,threshold=threshold,verbose=verbose,...)
  y[which(is.infinite(y))] <- NA
  invisible(y)
}

#' @exportS3Method
#' @export
annual.spell <- function(x,FUN='mean',nmin=0,threshold=NULL,verbose=FALSE,...) {
  attr(x,'names') <- NULL
  if ( (inherits(x,'mon'))  & is.null(nmin) ) {
    iy <- year(x)
    nmy <- as.numeric(table(iy))
    full <- nmy[nmy==12]
    x[is.element(iy,!full),] <- NA
    na.rm=FALSE
  }
  #y <- annual.default(x,FUN=match.fun(FUN),...)
  y <- annual.default(x,FUN=FUN,nmin=nmin,threshold=threshold,...) 
  invisible(y)
}

#' @exportS3Method
#' @export
annual.dsensemble <- function(x,FUN='mean',verbose=FALSE,...) {
  if (verbose) print("annual.dsensemble")
  clsx <- class(x)
  clss <- class(attr(x,'station'))
  if (!inherits(x,c('day','month','annual','season')))
    class(x) <- c(clsx[1],clss[2],clsx[2])
  if (inherits(x,'season'))
    y <- subset(x,it=0,verbose=verbose)
  else
    y <- annual.default(x,FUN=FUN,verbose=verbose,...)
  
  attr(y,'station') <- annual.station(attr(x,'station'),...)
  names(y) <- names(x)
  invisible(y)
}

#' @exportS3Method
#' @export
annual.field <- function(x,FUN='mean',na.rm=TRUE,nmin=NULL,verbose=FALSE, ...) {
  if (verbose) print('annual.field')
  attr(x,'names') <- NULL
  if (inherits(FUN,'function')) FUN <- deparse(substitute(FUN)) # REB110314
  yr <- year(x)
  cls <- class(x)
  #  class(x) <- "zoo"
  if ( (inherits(x,'mon'))  & is.null(nmin) ) {
    iy <- year(x)
    nmy <- as.numeric(table(iy))
    full <- nmy[nmy==12]
    x[is.element(iy,!full),] <- NA
    na.rm=FALSE
  }
  #  y <- aggregate(x,yr,FUN=match.fun(FUN),...,na.rm=na.rm)
  #  y <- aggregate(x,yr,FUN=FUN,...,na.rm=na.rm)
  y <- annual.default(x,FUN=FUN,nmin=nmin,verbose=verbose,...) 
  y <- attrcp(x,y)
  attr(y,'history') <- history.stamp(x)
  attr(y,'dimensions') <- c(attr(x,'dimensions')[1:2],length(index(y)))
  class(y) <- cls
  # KMP 2017-10-25: time resolution is not always second in class vector, e.g., for an object 
  # of class [eof, comb, field, month, zoo] the following line will replace comb instead of month
  #class(y)[2] <- "annual"
  class(y)[length(cls)-1] <- "annual"
  y[which(is.infinite(y))] <- NA
  invisible(y)
}

#' @exportS3Method
#' @export
annual.eof <- function(x,FUN='mean',na.rm=TRUE,nmin=NULL,verbose=FALSE, ...) {
  if (verbose) print('annual.eof')
  attr(x,'names') <- NULL
  if (inherits(FUN,'function')) FUN <- deparse(substitute(FUN)) # REB110314
  yr <- year(x)
  cls <- class(x)
  #  class(x) <- "zoo"
  if ( (inherits(x,'mon'))  & is.null(nmin) ) {
    iy <- year(x)
    nmy <- as.numeric(table(iy))
    full <- nmy[nmy==12]
    x[is.element(iy,!full),] <- NA
    na.rm=FALSE
  }
  
  y <- annual.default(x,FUN=FUN,nmin=nmin,verbose=verbose,...) 
  y <- attrcp(x,y)
  attr(y,'history') <- history.stamp(x)
  attr(y,'dimensions') <- c(attr(x,'dimensions')[1:2],length(index(y)))
  class(y) <- cls
  # KMP 2017-10-25: time resolution is not always second in class vector, e.g., for an object 
  # of class [eof, comb, field, month, zoo] the following line will replace comb instead of month
  #class(y)[2] <- "annual"
  class(y)[length(cls)-1] <- "annual"
  y[which(is.infinite(y))] <- NA
  invisible(y)
}

#' @export as.annual
as.annual <- function(x, ...) UseMethod("as.annual")

#' @exportS3Method
#' @export
as.annual.default <- function(x, ...) annual(x, ...)

#' @exportS3Method
#' @export
as.annual.numeric <- function(x, ...) annual(x, ...)

#' @exportS3Method
#' @export
as.annual.integer <- function(x, ...) structure(x, class = "annual")

#' @exportS3Method
#' @export
as.annual.yearqtr <- function(x, frac = 0, ...) {
  if (frac == 0) annual(as.numeric(x)) else
    as.annual(as.Date(x, frac = frac), ...)
}

#' @exportS3Method
#' @export
as.annual.station <- function(x, ...) annual.station(x,...)

#' @exportS3Method
#' @export
as.annual.spell <- function(x, ...) annual.spell(x,...)

#' @export
as.monthly <- function(x,...) UseMethod("as.monthly")

yyyymm <- function(x) ym <- as.Date(paste(year(x),month(x),'01',sep='-'))

#' @exportS3Method
#' @export
as.monthly.default <- function(x,...) {
  y <- aggregate(x,by=yyyymm,...)
  return(y)
}

#' @exportS3Method
#' @export
as.monthly.field <- function(x,FUN='mean',...) {
  if (inherits(x,'month')) return(x)
  y <- aggregate(as.zoo(x), yyyymm, #function(tt) as.Date(as.yearmon(tt)),
                 FUN=FUN,...)
  y <- attrcp(x,y)
  attr(y,"dimensions") <- c(attr(x,"dimensions")[1:2],length(index(y)))
  attr(y,'history') <- history.stamp(x)
  class(y) <- class(x)
  class(y)[2] <- "month" 
  return(y)
}


#' @exportS3Method
#' @export
## This is a dublicate of that in as.R
as.monthly.station <- function (x, FUN = "mean", ...) {
  y <- aggregate(zoo(x), yyyymm, #function(tt) as.Date(as.yearmon(tt)), 
                 FUN = FUN, ...)
  y <- attrcp(x, y)
  attr(y, "history") <- history.stamp(x)
  class(y) <- class(x)
  class(y)[2] <- "month"
  return(y)
}

#' @export
as.daily <- function(x,...) UseMethod("as.daily")

yyyymmdd <- function(x) ymd <- as.Date(paste(year(x),month(x),day(x),sep='-'))

#' @exportS3Method
#' @export
as.daily.default <- function(x,...) {
  y <- aggregate(x,by=yyyymmdd,...)
  return(y)
}

#' @exportS3Method
#' @export
as.daily.field <- function(x,FUN='mean',...) {
  if (inherits(x,'month')) return(x)
  y <- aggregate(as.zoo(x), yyyymmdd, #function(tt) as.Date(as.yearmon(tt)),
                 FUN=FUN,...)
  y <- attrcp(x,y)
  attr(y,"dimensions") <- c(attr(x,"dimensions")[1:2],length(index(y)))
  attr(y,'history') <- history.stamp(x)
  class(y) <- class(x)
  class(y)[2] <- "day" 
  return(y)
}

#' @exportS3Method
#' @export
## This is a dublicate of that in as.R
as.daily.station <- function (x, FUN = "mean", ...) {
  y <- aggregate(zoo(x), yyyymmdd, #function(tt) as.Date(as.yearmon(tt)), 
                 FUN = FUN, ...)
  y <- attrcp(x, y)
  attr(y, "history") <- history.stamp(x)
  class(y) <- class(x)
  class(y)[2] <- "day"
  return(y)
}

#' @export
as.4seasons <- function(x,...) UseMethod("as.4seasons")

#' @exportS3Method
#' @export
as.4seasons.default <- function(x,...,FUN='mean',slow=FALSE,verbose=FALSE,nmin=NULL) {
  if(verbose) print('as.4seasons.default')
  if (inherits(x,c('season','seasonal'))) return(x)
  attr(x,'names') <- NULL
  d <- dim(coredata(x))
  #print(d)
  if (is.null(d)) d <- c(length(x),1)
  if (!slow) {
    if (inherits(x,"month")) {
      if ( (is.null(d)) | (d[2]==1) ) {
        X <- c(NA,coredata(x)[1:length(x)-1]) # shift the coredata by 1 to start on December. This works only for monthly data !!!  
      } else {
        # shift the coredata by 1 to start on December. slow...
        if (verbose) print('Shift series')
        X <- rbind(rep(NA,d[2],1),coredata(x)[1:d[1]-1,])
      }
      if (verbose) print(dim(X))
      X <- zoo(X,order.by=index(x))
      ##yrseas <- fourseasons(ix)
      ##print(yrseas)
      if (verbose) print('aggregate')
      #print(names(list(...)))
      yq <- function(t) as.yearqtr(year(t) + 0.25*floor((month(t)-1)/3))
      y <- aggregate(x=as.zoo(X),by=yq,#as.yearqtr,
                     FUN=match.fun(FUN),...)
      # convert yearqtr to yearmon
      ## Remove season values with less than nmin data points
      if(is.null(nmin)) nmin <- 3
      nd <- aggregate(x=as.zoo(X),by=yq,FUN=nv)
      ok <- nd >= nmin  
      coredata(y)[!ok] <- NA
      if (verbose) print('define y')
      y <- zoo(x=y,order.by=as.Date(as.yearmon(index(y))))
    } else y <- as.4seasons.day(x,FUN=FUN,nmin=nmin,verbose=verbose,...)
    #y <- as.4seasons.day(x,FUN=match.fun(FUN),...)
    
    #print(dim(y))
    ok <- length(index(y))
    #print(summary(c(coredata(y))))
  } else {
    yr <- sort(rep(as.integer(rownames(table(year(x)))),4))
    n <- length(yr)
    
    q <- rbind(c(12,1,2),3:5,6:8,9:11)
    X <- matrix(rep(NA,n*d[2]),n,d[2]) 
    t <-rep(NA,n)
    
    if (verbose) print("start loop")
    for (i in 1:n) {
      iq <- (i-1) %% 4 + 1
      if (iq == 1) 
        ii <- (is.element(year(x),yr[i]) &
                 is.element(month(x),c(1,2))) |
          (is.element(year(x),yr[i]-1) &
             is.element(month(x),12))  else
               ii <-  is.element(year(x),yr[i]) &
                 is.element(month(x),q[iq,])
             if (d[2]==1) cline <- paste(FUN,"(coredata(x[ii,]),...)",sep="") else
               cline <- paste("apply(coredata(x[ii,]),2,",FUN,", ...)",sep="")
             #print(cline)
             if ( (inherits(x,'day')) & (sum(ii)>=85) |
                  (inherits(x,'month')) & (sum(ii)>=3) ) {
               X[i,] <- eval(parse(text=cline))
               t[i] <- yr[i] + (iq-1)/4
               print(c(i,yr[i],round(mean(X[i,]),2),iq,sum(ii),round(X[i],2),t[i]))
             }
    } 
    if (verbose) print("end loop")
    ok <- is.finite(rowMeans(X,na.rm=TRUE)) & is.finite(t)
    if (verbose) print(table(as.yearqtr(t[ok])))
    #print(summary(c(X)))
    y <- zoo(X[ok,],order.by=as.Date(as.yearqtr(t[ok])))
    #names(y) <- FUN
  }
  if (!is.null(dim(y))) {
    ok <- is.finite(rowMeans(y,na.rm=TRUE))
    y <- y[ok,]
  }
  if (verbose) print('attributes')
  y <- attrcp(x,y)
  attr(y,'history') <- history.stamp(x)
  attr(y,'season.interval') <- "4seasons"
  if (inherits(x,'field'))
    attr(y,'dimensions') <- c(attr(x,'dimensions')[1:2],sum(ok))
  class(y) <- class(x)
  class(y)[2] <- "season"
  #plot(y)
  return(y) 
}

#' @exportS3Method
#' @export
as.4seasons.day <- function(x,...,FUN='mean',na.rm=TRUE,dateindex=TRUE,nmin=85,verbose=FALSE) {
  if(verbose) print('as.4seasons.day')
  if (inherits(x,'month')) nmin <- 3 # AM 06-07-2015
  attr(x,'names') <- NULL  
  t <- index(x)
  year <- year(t) #as.numeric(format(t,'%Y'))
  month <- month(t) #as.numeric(format(t,'%m'))
  day <- day(t) # as.numeric(format(t,'%d'))
  #shift the time stamps by one month, sneaking December into the subsequent year
  month <- month + 1
  dec <- is.element(month,13)
  year[dec] <- year[dec] + 1
  month[dec] <- 1
  # Change the day to avoid warning that the calendar is wrong (e.g. due to
  # too many days in February). Since the data is aggregated, the exact day
  # in the month doesn't matter here.
  hour <- 12*(day - 2*trunc(day/2))
  day <- trunc(day/2) + 1
  #print(table(year)); print(table(month));
  #print(table(day)); print(table(hour));   
  #tshifted <- as.Date(paste(year,month,day,sep="-"))
  tshifted <-  ISOdate(year=year,month=month,day=day,hour=hour)
  #print(summary(tshifted))
  X <- zoo(coredata(x),order.by=tshifted)
  
  # Test for the presens of 'na.rm' in argument list - this is a crude fix and not a
  # very satisfactory one. Fails for FUN==primitive function.
  if (is.function(FUN)) test.na.rm <- FALSE else
    test.na.rm <- (sum(is.element(FUN,c('mean','min','max','sum','quantile')))>0)
  if ( (sum(is.element(names(formals(FUN)),'na.rm')==1)) | (test.na.rm) )
    y <- aggregate(X,as.yearqtr,FUN=match.fun(FUN),...,na.rm=na.rm) else
      y <- aggregate(X,as.yearqtr,FUN=match.fun(FUN),...)
  
  # Set to missing for seasons with small data samples:
  nd <- aggregate(X,as.yearqtr,FUN=nv)
  ok <- nd >= nmin
  coredata(y)[!ok] <- NA
  # dateindex: convert "1775 Q1" to "1775-01-01"
  if (dateindex) {
    y <- zoo(coredata(y),order.by=as.Date(index(y)))
  }
  unit <- attr(y,'unit')
  y <- attrcp(x,y,ignore=c("unit","names"))
  unit -> attr(y,'unit')
  #str(y); print(unit)
  attr(y,'history') <- history.stamp(x)
  attr(y,'season.interval') <- "4seasons"
  class(y) <- class(x)
  class(y)[2] <- "season"
  invisible(y)
}

#' @exportS3Method
#' @export
as.4seasons.station <- function(x,...,FUN='mean') {
  #print('as.4seasons.station')
  y <- as.4seasons.default(x,FUN=FUN,...)
  #  y <- attrcp(x,y)
  #  attr(y,'history') <- history.stamp(x)
  #  class(y) <- class(x)
  #  class(y) <- gsub("month","season",class(x))
  return(y) 
}

#' @exportS3Method
#' @export
as.4seasons.spell <- function(x,...,FUN='mean') {
  y <- as.4seasons.default(as.station(x),FUN=FUN,...)
  #  y <- attrcp(x,y)
  #  attr(y,'history') <- history.stamp(x)
  #  class(y) <- class(x)
  #  class(y) <- gsub("month","season",class(x))
  return(y) 
}

#' @exportS3Method
#' @export
as.4seasons.field <- function(x,...,FUN='mean',verbose=FALSE) {
  if(verbose) print("as.4seasons.field")
  d <- attr(x,"dimensions")
  y <- as.4seasons.default(x,...,FUN=FUN,verbose=verbose)
  y <- attrcp(x,y)
  attr(y,'history') <- history.stamp(x)
  ## Update dimensions
  attr(y, "dimensions") <- c(d[1],d[2],dim(y)[1])
  class(y) <- class(x)
  class(y) <- gsub("month","season",class(x))
  return(y)
}

#' @exportS3Method
#' @export
as.4seasons.dsensemble <- function(x,...,FUN='mean') {
  cls <- class(x)
  class(x) <- c("station",cls[2],"zoo") ## AM 06-07-2015 Quick fix here, time step added into the class of x
  attrx <- attributes(x)
  y <- as.4seasons.station(x,FUN=FUN,...)
  ##attributes(y) <- attrx
  
  y <- attrcp(x,y)
  attr(y,"station") <- as.4seasons.station(attr(x,"station"))
  
  attr(y,'history') <- history.stamp(x)
  class(y) <- c("dsensemble","season","zoo")
  return(y)
}

# do not export - local function only used in as.seasons
leapdate <- function(years="2000", dates="02-29") {
  yeardate <- paste(years, dates, sep="-")
  nok <- !leapyear(years) & dates=="02-29"
  yeardate[nok] <- paste(years[nok], "02-28", sep="-")
  return(as.Date(yeardate))
}

# Not to confuse with season
# This function extracts a given seasonal interval and aggregates a given statistic
#' @export
as.seasons <- function(x,start='01-01',end='12-31',FUN='mean',verbose=FALSE,...) {
  if(verbose) print("as.seasons")
  yrs <- year(x); d <- dim(x)
  # ns = number of stations
  if (is.null(d)) ns <- 1 else ns <- d[2]
  years <- as.numeric(rownames(table(yrs))); n <- length(years)
  y <- matrix(rep(NA,n*ns),n,ns); k <- y
  if(is.numeric(start) & is.numeric(end)) {
    if(verbose) print("input 'start' and 'end' are likely months")
    if(start>=10) start <- paste0(start,"-01") else start <- paste0("0",start,"-01")
    if(end==2) {
      end <- "02-29"
    } else {
      if(end<10) end <- paste0("0",end)
      ym <- as.yearmon(paste0("2020-",end,"-01"))
      days <- as.Date(ym, frac = 1) - as.Date(ym) + 1
      end <- paste0(end, "-", days)
    }
  } else {
    if(verbose) print("input 'start' and 'end' are likely dates (mm-dd)")
  }
  
  start.1 <- as.numeric(leapdate(years[1], start))
  end.1 <- as.numeric(leapdate(years[1], end))
  if (start.1 > end.1) twoyears <- 1 else twoyears <- 0
  if(verbose) print(paste(start, end))
  
  for (i in 1:n) {
    if(verbose) print(paste("Aggregate for year",years[i]))
    z <- coredata(window(x, start=leapdate(years[i], start),
                         end=leapdate(years[i]+twoyears, end)))
    k[i,] <- apply(matrix(z,ceiling(length(z)/ns),ns),2,nv)
    y[i,] <- apply(matrix(z,ceiling(length(z)/ns),ns),2,FUN, ...)
  }
  y <- zoo(y,order.by=as.Date(paste(years,start,sep='-')))
  y <- attrcp(x,y)
  attr(y,'history') <- history.stamp(x)
  if (twoyears==0) {
    attr(y,'season.interval') <- paste(start,'to',end)
  } else {
    attr(y,'season.interval') <- paste(start,'to',end,'the following year')
  }
  attr(y,'n.valid') <- k
  class(y) <- class(x)
  class(y)[2] <- "season"
  #class(y)[2] <- "annual"
  return(y)
}

## @RasmusBenestad, 2021-04-12
## Summary statistics for the rainy season in Africa: October to March.
## Function that processes the data - it assumes daily precipitation
#' @export
as.OctMar <- function(x,FUN='sum',nmin=90,plot=FALSE,verbose=FALSE) {
  ## x is a station object from esd
  ## Step 1: Oct-Dec from one year
  if (verbose) {print('as.OctMar'); print(class(x))}
  if (!is.precip(x)) warning("as.OctMar was designed for daily rainfall data, but that's OK")
  OctDec <- subset(x,it=month.abb[10:12])
  ## Step 2: Jan - Mar from another year
  JanMar <- subset(x,it=month.abb[1:3])
  ## Now we need to take the sum over the months using 'annual'
  OctDec <- annual(OctDec,FUN=FUN,nmin=nmin)
  JanMar <- annual(JanMar,FUN=FUN,nmin=nmin)
  ## R will add the data with corresponding years
  ## index() controls the time information
  index(OctDec) <- year(OctDec)-1
  index(JanMar) <- year(JanMar)
  ## Check: 
  #print(range(index(OctDec)))
  #print(range(index(JanMar)))
  ## Add the Jan-Mar sum with the previous years Oct-Dec sum
  if (FUN=='sum') OctMar <- JanMar + OctDec else
    OctMar <- 0.5*(JanMar + OctDec)
  #print(range(index(OctMar)))
  OctMar <- attrcp(JanMar,OctMar)
  class(OctMar) <- class(JanMar)
  attr(OctMar,'season.interval') <- "10-01 to 03-31 the following year"
  
  if (plot==TRUE) {
    ## if the plot argument == TRUE, then do this:
    y <- as.matrix(OctMar)
    ## Here we set the y-axix label and wexpress y in % if FUN=='wetfreq'
    if (FUN=='wetfreq') {
      y <- y * 100
      ylab='%'
    } else if (is.precip(x)) ylab <- 'mm'
    ## Assign row names: the year and following year
    rownames(y) <- paste(year(OctMar),year(OctMar)+1,sep='-')
    ## Make a graph with bars
    #plot(y[,1],type='l') ## If you want to plot lines
    ## Loop over all the stations:
    ns <- length((loc(x)))
    ## Loop over stations
    for (i in 1:ns) {
      par(las=2,cex.axis=0.5)
      ## This line is to make a more sensible title for most people:
      fun <- switch(FUN,'mean'='mean','sum'='Total rainfall',
                    'wetfreq'='wet day frequency',
                    'wetmean'='mean intensity')
      ## Here we make a title text which gives additional information
      ## about the plot and the data record.
      main=paste('Rainy season for',loc(x)[i],':',fun,
                 'trend=',round(100*trend.coef(y[,i])/mean(y[,i],na.rm=TRUE),1),'%/decade')
      ## Show a bar plot for the data, but only when it's not missing
      barplot(y[is.finite(y[,i]),i],main=main,col='blue',
              ylab=ylab)
      ## Add a line showing the median value:
      lines(c(1,length(y)),rep(median(y[,i],na.rm=TRUE),2),lty=2)
      lines(c(1,length(y)),rep(mean(y[,i],na.rm=TRUE),2),lty=3)
      lines(trend(y[,i]),col='red',lwd=2)
      grid()
    }
  }
  return(OctMar)
}

## @RasmusBenestad, 2021-04-12
## A function to assist more flexible definition of a 'year' that may start any day between
## January 1st and December 31st. The argument start may be a calendar name in {month.abb} or
## defined 
#' @export
shiftyear <- function(x,start,verbose=FALSE) {
  x0 <- x
  if (verbose) print(paste('shiftyear: The year start is',start))
  if (inherits(x,'season')) warning('annual: does not support start for seasonal aggregates')
  imon <- grep(start,month.abb,ignore.case = TRUE)
  ## If start is a month ('Jan',...'Dec)
  if (length(imon)>0) {
    ## If daily data, need to find he number of days into the year
    if (inherits(x,'day')) {
      yyyy1 <- year(x)[1]
      t1 <- as.Date(paste(yyyy1,'01-01',sep='-'))
      t2 <- as.Date(paste(yyyy1,imon,'01',sep='-'))
      imon <- t2 - t1
    } else imon <- imon - 1
    if (verbose) print(imon)
    x <- lag(x,imon) 
  } else if (is.character(start)) { 
    if (nchar(start)==5) {
      ## if start is in format 'MM-DD'
      yyyy1 <- year(x)[1]
      t1 <- as.Date(paste(yyyy1,'01-01',sep='-'))
      t2 <- as.Date(paste(yyyy1,imon,sep='-'))
      imon <- t2 - t1
      if (verbose) print(imon)
      x <- lag(x,imon)
    }
  } else if (is.numeric(start)) x <- lag(x,start)
  x <- attrcp(x0,x)
  return(x)
}
metno/esd documentation built on April 29, 2024, 3:34 p.m.