R/eventMain.R

Defines functions hsGetEvent eventsOnChange timeDifferencesToPauses eventPauses .eventColumns .checkEvents .removeAndWarnIfColumnExists toEvents eventDuration hsEvents hsEventsToUnit hsSigWidth hsEventNumber getXLimFromEventLists eventToXLim .lookupTableTimeUnitToSeconds .stopOnUnknownTimeUnit .getSecondsPerTimeUnitOrStop .getSignalWidth .getTimeUnit .getCurrentTimeUnitOrStop eventLimits indicesOfEventsContainedInEvent indicesOfEventsContainingEvent invertedEvents renumberEvents .replaceZeroWithOne eventsByState

Documented in eventDuration eventLimits eventPauses eventsByState eventsOnChange eventToXLim getXLimFromEventLists hsEventNumber hsEvents hsEventsToUnit hsGetEvent hsSigWidth indicesOfEventsContainedInEvent indicesOfEventsContainingEvent invertedEvents renumberEvents timeDifferencesToPauses toEvents

# eventsByState ----------------------------------------------------------------

#' Get Events by Evaluation of a State Variable
#' 
#' @param timestamps vector of timestamps (POSIXct)
#' @param states vector of state values in which each element corresponds to one
#'   timestamp in \emph{timstamps}. If the state is the value given in
#'   \emph{in.state}) the corresponding times are considered to be lying within
#'   an event. If the state is the value given in \emph{out.state}) the
#'   corresponding times are considered to be lying out of an event. For values
#'   that are neither of the values given in \emph{in.state} and
#'   \emph{out.state}, respectively, the corresponding timestamps are considered
#'   to either belong to an event or not, depending on the previous clear state
#'   ("in" or "out") in the sequence of states.
#' @param eventSeparationTime same meaning as in \code{\link{hsEvents}}
#' @param signalWidth see description in \code{\link{hsEvents}}
#' @param in.state value in \emph{states} indicating the state "in event".
#'   Default: 1
#' @param out.state value in \emph{states} indicating the state "out of event".
#'   Default: 0
#' @return event characteristics (begin, end, duration, ...) in a data frame, as
#'   returned by \code{\link{hsEvents}}
#' @export
#' @examples 
#'   
#' # Generate random timestamps
#' starttime <- as.POSIXct("2015-03-12 10:51")
#'     
#' n <- 100
#' timestamps <- seq(starttime, by = 60, length.out = n)
#' values <- rnorm(n)
#'   
#' # Give values above 1 the state "in" and values below -1 the state "out"
#' states <- rep("", times = n)
#' states[values > 1] <- "in"
#' states[values < -1] <- "out"
#'   
#' # Generate the events
#' events <- eventsByState(
#'   timestamps, states, eventSeparationTime = 5 * 60, in.state = "in", 
#'   out.state = "out", signalWidth = 60
#' )
#'   
#' # Prepare a vector of colours
#' col <- rep("black", length(states))
#' col[states == "in"] <- "green"
#' col[states == "out"] <- "red"
#'   
#' # Plot the values, the threshold lines and a legend
#' graphics::plot(timestamps, values, type = "l", ylim = c(-5, 5))
#' points(timestamps, values, col = col)
#'   
#' graphics::abline(h = c(1, -1), lty = 2)
#'   
#' legend(
#'   "topright", bty = "n", legend = c("in", "out"), col = c("green", "red"), 
#'    pch = 1, bg = "white", horiz = TRUE
#' )
#'   
#' # Plot the event borders
#' ganttPlotEvents(events, add = TRUE, y1 = -5, y2 = 4)  
#'
eventsByState <- function(
  timestamps, states, eventSeparationTime, signalWidth, in.state = 1, 
  out.state = 0
)
{    
  stopifnot(length(timestamps) == length(states))
  
  clearStates <- c(in.state, out.state)
  
  interState <- unique(states[! (states %in% clearStates)])
  
  if (length(interState) > 1) {
    
    stop(
      "There is more than one intermediate state: ", 
      kwb.utils::stringList(interState)
    )
  }
  
  # "events" (= index intervals from iBeg to iEnd) of changing section numbers
  events <- eventsOnChange(states, include.value = TRUE)
  
  # indices of intermediate states (state == "")
  transition <- which(! (events$value %in% clearStates))
  
  # assign the last state in.state or out.state to the intermediate states.
  # If the very first element is in intermediate state, it keeps intermediate.
  events$value[transition] <- events$value[.replaceZeroWithOne(transition - 1)]
  
  onTimes <- hsGetEvent(
    tSeries = data.frame(myDateTime = timestamps),    
    events = events, 
    evtnums = which(events$value == in.state), 
    useIndex = TRUE
  )$myDateTime
  
  hsEvents(onTimes, evtSepTime = eventSeparationTime, signalWidth = signalWidth)
}

# .replaceZeroWithOne ----------------------------------------------------------

.replaceZeroWithOne <- function(x)
{
  x[x == 0] <- 1
  x
}

# renumberEvents ---------------------------------------------------------------

#' renumberEvents
#' 
#' add event number (= real row number) in column \emph{event}
#' 
#' @param events event information as returned by \code{\link{hsEvents}}
#' @return data frame with (additional) column \emph{event}
#' @export
renumberEvents <- function(events)
{
  events$event <- seq_len(nrow(events))
  
  events
}

# invertedEvents ---------------------------------------------------------------

#' Events to Gaps between Events
#' 
#' "inverted" events: gaps between ends of events and begins of next events
#' 
#' @param events data frame with columns \emph{tBeg} (begin of event) and
#'   \emph{tEnd} (end of event), representing events
#' @export
invertedEvents <- function(events)
{
  .checkEvents(events)
  
  signalWidth <- .getSignalWidth(events)
  
  toEvents(
    data.frame(
      tBeg = events$tEnd[-nrow(events)], 
      tEnd = events$tBeg[-1]
    ),
    signalWidth = ifelse(is.null(signalWidth), "NA", signalWidth)
  )
}

# indicesOfEventsContainingEvent -----------------------------------------------

#' indicesOfEventsContainingEvent
#' 
#' indicesOfEventsContainingEvent
#' 
#' @param events data frame with columns \emph{tBeg}, \emph{tEnd}
#' @param event data frame of one row with columns \emph{tBeg}, \emph{tEnd}
#' 
#' @return vector of indices representing the positions of the events in 
#'   \emph{events} in which \emph{event} is fully contained
#' @export
indicesOfEventsContainingEvent <- function(events, event)
{
  .checkEvents(events)
  
  stopifnot(nrow(event) == 1) 
  
  which(event$tBeg >= events$tBeg & event$tEnd <= events$tEnd)
}

# indicesOfEventsContainedInEvent ----------------------------------------------

#' indicesOfEventsContainedInEvent
#' 
#' @param events data frame with columns \emph{tBeg}, \emph{tEnd}
#' @param event data frame of one row with columns \emph{tBeg}, \emph{tEnd}
#' 
#' @return vector of indices representing the positions of the events in 
#'   \emph{events} that are fully contained in \emph{event}
#' @export
indicesOfEventsContainedInEvent <- function(events, event)
{
  .checkEvents(events)
  
  stopifnot(nrow(event) == 1) 
  
  which(event$tBeg <= events$tBeg & event$tEnd >= events$tEnd)
}

# eventLimits ------------------------------------------------------------------

#' only tBeg and tEnd of events (possibly extended)
#' 
#' return only the limits \emph{tBeg} and \emph{tEnd} of the event, possibly 
#'   extended by a "context"
#' 
#' @param events events as returned by e.g. \code{\link{toEvents}}
#' @param context Vector of two elements giving the "context" before and after
#'   the event to be plotted, in percentage of event duration. e.g. c(0.1, 0.2)
#'   means that the time interval to be plotted starts 10 percent of the event 
#'   duration before the event begin and ends 20 percent of the event duration 
#'   after the end of the event.
#' @param absolute if TRUE, the context values are interpreted as absolute
#'   values (seconds) instead of fractions of the event duration. Default: FALSE
#' @return data frame with columns \emph{tBeg} and \emph{tEnd}, taken from 
#'   \emph{events} and possibly reduced (tBeg) and/or extended (tEnd) by a 
#'   fraction of the event duration (read from column \emph{dur} in 
#'   \emph{events}).
#' @export
eventLimits <- function(events, context = c(0, 0), absolute = FALSE)
{
  if (absolute) {
    
    durationInSeconds <- 1
    
  } else {
    
    timeUnit <- .getCurrentTimeUnitOrStop(events)
    secondsPerTimeUnit <- .getSecondsPerTimeUnitOrStop(timeUnit)
    durationInSeconds <- events$dur * secondsPerTimeUnit    
  }
  
  data.frame(
    tBeg = events$tBeg - context[1] * durationInSeconds,
    tEnd = events$tEnd + context[2] * durationInSeconds
  )
}

# .getCurrentTimeUnitOrStop ----------------------------------------------------

.getCurrentTimeUnitOrStop <- function(events) 
{
  currentTimeUnit <- .getTimeUnit(events)
  
  if (is.null(currentTimeUnit))
    stop("Event data frame does not provide the attribute \"tUnit\".")  
  
  currentTimeUnit
}

# .getTimeUnit -----------------------------------------------------------------

.getTimeUnit <- function(events) 
{
  attr(events, "tUnit")  
}

# .getSignalWidth --------------------------------------------------------------

.getSignalWidth <- function(events, default = 0) 
{
  signalWidth <- attr(events, "signalWidth")
  
  if (is.null(signalWidth)) {
    
    # try to determine the signal width from tBeg, tEnd and dur
    if (all(.eventColumns(duration = TRUE) %in% names(events))) {
      
      signalWidth <- hsSigWidth(events)
    } else {
      
      signalWidth <- default
      
      messageText <- paste(
        "Signal width is set to", default, "(seconds) since no value is given."
      )
      
      if (!is.na(default) & default == 0) {
        messageText <- paste (
          messageText, "This may lead to events of duration 0!"
        )
      }
    }
    
    warning(messageText)    
  }
  
  signalWidth
}

# .getSecondsPerTimeUnitOrStop -------------------------------------------------

.getSecondsPerTimeUnitOrStop <- function(timeUnit)
{
  .stopOnUnknownTimeUnit(timeUnit)
  
  .lookupTableTimeUnitToSeconds()[[timeUnit]]
}

# .stopOnUnknownTimeUnit -------------------------------------------------------

.stopOnUnknownTimeUnit <- function(timeUnit, type = "")
{
  knownUnits <- names(.lookupTableTimeUnitToSeconds())
  
  if (! (timeUnit %in% knownUnits)) {
    
    stop(sprintf(
      "%s time unit \"%s\" not in list of supported units (%s)", 
      type, timeUnit, kwb.utils::stringList(knownUnits)
    ))
  }
}

# .lookupTableTimeUnitToSeconds ------------------------------------------------

.lookupTableTimeUnitToSeconds <- function()
{
  data.frame(s = 1, min = 60, h = 3600, d = 86400)  
}

# eventToXLim ------------------------------------------------------------------

#' tBeg and tEnd of event to two-element vector
#' 
#' puts tBeg and tEnd of event into a vector of two POSIXct elements
#' 
#' @param event data frame with columns \emph{tBeg}, \emph{tEnd}
#' @return vector of two elements: \emph{tBeg} and \emph{tEnd} from
#'   \emph{event}, both converted to UTC timezone
#' @export
eventToXLim <- function(event)
{
  kwb.datetime::toUTC(c(event$tBeg, event$tEnd))
}

# getXLimFromEventLists --------------------------------------------------------

#' overall first begin and last end of events
#' 
#' minimum tBeg and maximum tEnd found in event lists
#' 
#' @param eventLists list of data frames containing events (containing columns
#'   \emph{tBeg}, \emph{tBeg}, as returned by \code{\link{hsEvents}})
#'   
#' @return vector of two elements: the first begin (minimum of tBeg) and the
#'   last end (maximum of tEnd), found in any of the event data frames given in 
#'   \emph{eventLists}
#' @export
getXLimFromEventLists <- function(eventLists)
{
  n <- length(eventLists)
  
  for (i in seq_len(n)) {
    
    events <- eventLists[[i]]
    
    if (i == 1) {
      
      firstBegin <- min(events$tBeg)
      lastEnd <- max(events$tEnd)
      
    } else {
      
      firstBegin <- min(firstBegin, min(events$tBeg))
      lastEnd <- max(lastEnd, max(events$tEnd))      
    }
  }
  
  c(firstBegin, lastEnd)
}

# hsEventNumber ----------------------------------------------------------------

#' number timestamps according to event information
#' 
#' numbering timestamps according to event information
#' 
#' @param tstamps vector of timestamps
#' @param events event information as returned by \code{\link{hsEvents}}
#' @param eventNumbers optional vector of event numbers with as many elements as
#'   there are rows in \emph{tstamps}. Default: \code{seq_len(nrow(events))}
#' @param commaSeparated if there are timestamps taht belong to more than one
#'   event, the default behaviour (commaSeparated = FALSE) of this function is
#'   to return a list with each list element being a vector of integer numbers
#'   representing the numbers of events to which the corresponding timestamps
#'   belong. With commaSeparated = TRUE, the list of event numbers is converted
#'   into a vector of character where each element is a text string in which
#'   more than one event number are separated by a comma. E.g. c("1", "1,2",
#'   "2") would be returned if the first timestamp belongs to event 1, the
#'   second to both event 1 and 2, and the third to event 2.
#' @export
hsEventNumber <- function(
  tstamps, events, eventNumbers = seq_len(nrow(events)), commaSeparated = FALSE
)
{
  ## number of events
  nevts <- if (is.null(events)) {
    0
  } else {
    nrow(events)    
  }
  
  ## Return if there are no events
  if (nevts == 0) {
    
    return()
  }

  stopifnot(length(eventNumbers) == nevts)  
  
  ## Check if columns "tBeg" and "tEnd" exist
  .checkEvents(events)
  
  ## init result vector with NA
  #evtNumber <- rep(NA, length(tstamps))
  evtNumber <- as.list(rep(as.numeric(NA), length(tstamps)))
  
  tstamps <- as.double(tstamps)
  
  for (i in seq_len(nevts)) {
    
    indices <- which(
      "&"(tstamps >= as.double(events$tBeg[i]), 
          tstamps <= as.double(events$tEnd[i]))
    )
    
    #evtNumber[condition] <- eventNumbers[i]
    
    if (length(indices) > 0) {
      
      evtNumber[indices] <- sapply(
        evtNumber[indices], 
        FUN = function(x) c(stats::na.omit(x), eventNumbers[i]),
        simplify = FALSE
      )
    }
  }

  if (any(sapply(evtNumber, length) > 1)) {
    
    infotext <- "There are timestamps that belong to more than one event!\n"
    
    if (commaSeparated) {
      
      evtNumber <- sapply(evtNumber, paste, collapse = ",")
      
      infotext <- paste(
        infotext, 
        "A vector of character is returned where each element is a text string",
        "in which more than one event numbers are separated by a comma.")
      
    } else {
      
      infotext <- paste(infotext, 
      "A list of numeric vectors is returned. The vector at each list item",
      "[[i]] contains the numbers of the events to which the timestamp at",
      "corresponding index [i] in tstamps belongs (or NA if the corresponding",
      "timestamp does not belong to any event). Use 'commaSeparated = TRUE'",
      "to change this behaviour.")
    }
    
    warning(infotext)
    
  } else {
    
    evtNumber <- unlist(evtNumber)
  }

  evtNumber
}

# hsSigWidth -------------------------------------------------------------------

#' Find signal width in event list
#' 
#' Calculates signal width that was applied in event list \emph{evts}
#' 
#' @param evts data frame containing events (as e.g. provided by hsEvents)
#' @param dbg if \code{TRUE}, debug messages are shown.
#' @return signal width in seconds
#' @export
hsSigWidth <- function(evts, dbg = FALSE) 
{
  ## get names of required columns
  requiredColumns <- .eventColumns(duration = TRUE)
  
  ## Stop if evts is not a data frame or does not contain the required columns
  .checkEvents(evts, requiredColumns)
  
  ## Convert to seconds
  evts <- hsEventsToUnit(evts, "s")
  
  ## Calculate signal width from events in evts
  sigWidth <- evts$dur - (as.double(evts$tEnd) - as.double(evts$tBeg))
  
  if (dbg) {
    cat("sigWidth:\n")
    print(sigWidth)
  }
  
  ## Stop if there is more than one event and if the calculated signal widths 
  ## are not all equal 
  if (length(sigWidth) > 0 && ! all(sigWidth == sigWidth[1])) {
    
    evts$calcSigWidth <- sigWidth
    
    msg <- utils::capture.output(
      print(evts[, c(requiredColumns, "calcSigWidth")])
    )
    
    stop(
      "Unambiguous signal width encountered in event list:\n", 
      paste(msg, collapse = "\n")
    )
  }
  
  ## Return signal width
  sigWidth[1]  
}

# hsEventsToUnit ---------------------------------------------------------------

#' Time unit conversion for events
#' 
#' Converts event durations and pauses before and after events to the requested 
#' time unit. The time unit will be stored in the attribute \dQuote{tUnit} of
#' the returned data frame.
#' 
#' @param evts data frame representing events as provided by hsEvents
#' @param tUnit time unit to which durations and pauses shall be converted.
#' @return data frame containing events with durations (and pauses) given in the
#'   new time unit.
#' @export
hsEventsToUnit <- function(evts, tUnit)
{
  # current time unit must be given
  currentTimeUnit <- .getCurrentTimeUnitOrStop(evts)

  # time units must be known
  .stopOnUnknownTimeUnit(currentTimeUnit, "Current")
  .stopOnUnknownTimeUnit(tUnit, "Requested")

  ## Return the original events if current time unit equals requested time unit
  if (currentTimeUnit == tUnit) return(evts)
  
  ## Number of seconds per time unit
  sPerUnit <- .lookupTableTimeUnitToSeconds()
  
  ## Conversion factor between current time unit and requested time unit
  conv <- as.double(sPerUnit[currentTimeUnit]) / as.double(sPerUnit[tUnit])
  
  evts$dur <- conv * evts$dur
  if ("pBefore" %in% names(evts)) evts$pBefore <- conv * evts$pBefore
  if ("pAfter"  %in% names(evts)) evts$pAfter  <- conv * evts$pAfter
  
  ## Round to full seconds if target time unit is "s"
  if (tUnit == "s") {
    
    evts$dur <- round(evts$dur)
    
    if ("pBefore" %in% names(evts)) evts$pBefore <- round(evts$pBefore)
    if ("pAfter"  %in% names(evts)) evts$pAfter  <- round(evts$pAfter)
  }
  
  ## Set "tUnit" attribute
  attr(evts, "tUnit") <- tUnit
  
  ## Return data converted event data frame
  evts
}

# hsEvents ---------------------------------------------------------------------

#' Timestamp differences to events
#' 
#' Creates events from vector \emph{tseries} of timestamps based on time 
#' differences between consecutive timestamps in \emph{tseries}.
#' 
#' @param tseries vector containing a sorted list of timestamps.
#' @param evtSepTime \dQuote{event separation time} in seconds. Maximal allowed
#'   time difference between two consecutive timestamps within the same event.
#' @param signalWidth \dQuote{signal width} in seconds. Length of time interval
#'   that one timestamp is representing, e.g. \eqn{5*60 = 300} if each timestamp
#'   respresents a time interval of five minutes (as e.g. a time series is
#'   recorded on a five minute time scale). This parameter is needed to
#'   calculate event durations.
#' @param tUnit time unit of event duration and event pauses
#' @param pause if TRUE, pauses before and after the events are calculated
#' @param evtSepOp event separation operator, either "gt" or "ge". If
#'   \emph{evtSepOp} = "gt" (default) events are separated on time differences
#'   between two consecutive timestamps that are \emph{g}reater \emph{t}han
#'   \emph{evtSepTime}. If \emph{evtSepOp} = "ge" events are separated on time
#'   differences between two consecutive timestamps that are \emph{g}reater than
#'   or \emph{e}qual to \emph{evtSepTime}.
#' @param dbg if \code{TRUE}, debug messages are shown.
#' @param check.sorting if \code{TRUE}, it is checked whether the timestamps
#'   given in \code{tseries} are sorted and the program stops if this is not the
#'   case.
#' @return data frame with columns \emph{iBeg} and \emph{iEnd} indicating first
#'   and last index of the event in the \emph{tseries} vector, \emph{tBeg} and 
#'   \emph{tEnd} indicating first and last timestamp of the event and \emph{dur}
#'   indicating the event duration in seconds.
#' @seealso \code{\link{eventsOnChange}}
#' @export
hsEvents <- function(
  tseries, evtSepTime, signalWidth, tUnit = "s", pause = TRUE, evtSepOp = "gt",
  dbg = FALSE, check.sorting = FALSE
) 
{
  ## evtSepOp must be one of "gt", "ge"
  if (! (evtSepOp %in% c("gt", "ge"))) {
    
    stop(
      "evtSepOp must be one of \"gt\" (greater than) or \"ge\" (greater than ",
      "or equal to)"
    )
  }
  
  ## If tseries is a data frame, take the first column as timestamps
  if ("data.frame" %in% class(tseries)) {
    
    tseries <- kwb.utils::firstPosixColumn(tseries)
  }
  
  n <- length(tseries)
  
  kwb.utils::catIf(dbg, "n =", n, "\n")
  
  if (check.sorting) {
    
    if (! all(order(tseries) == seq_along(tseries))) {
      
      stop ("The series of timestamps is not sorted!")
    }
  }
  
  # Find indices after which a new event begins
  tPause <- diff(as.double(tseries))
  
  # Find pauses that are greater than or greater than or equal to evtSepTime
  ind <- if (evtSepOp == "gt") {
    
    # greater than 
    tPause > evtSepTime
    
  } else {
    
    # greater than or equal to
    tPause >= evtSepTime
  }
  
  # Find the indices of the event endings and begins
  iBeg <- c(1, (2:n)[ind])
  iEnd <- c((1:(n-1))[ind], n)
  
  # Find the corresponding times
  tBeg <- tseries[iBeg]
  tEnd <- tseries[iEnd]
  
  kwb.utils::printIf(dbg, ind)
  kwb.utils::printIf(dbg, iEnd)
  kwb.utils::printIf(dbg, iBeg)
  kwb.utils::printIf(dbg, tEnd)
  kwb.utils::printIf(dbg, tBeg)

  # Create a data frame with each row representing an event
  events <- data.frame(iBeg, iEnd, tBeg, tEnd)
  
  # add columns with event durations and pauses
  events <- toEvents(
    events = events, 
    signalWidth = signalWidth, 
    timeUnit = tUnit, 
    pause = pause, 
    timeDifferences = tPause[ind]
  )
  
  # Return result data frame with parameter values as attributes
  structure(
    events, 
    eventSeparationTime = evtSepTime,
    eventSeparationOperator = evtSepOp,
    signalWidth = signalWidth
  )
}

# eventDuration ----------------------------------------------------------------

#' Event Duration
#' 
#' @param tBeg timestamps representing the event begins
#' @param tEnd timestamps representing the event ends
#' @param signalWidth see description in \code{\link{hsEvents}}
#' @export
eventDuration <- function(tBeg, tEnd, signalWidth)
{
  as.double(tEnd) - as.double(tBeg) + signalWidth
}

# toEvents ---------------------------------------------------------------------

#' Convert to Data Frame of Events
#' 
#' @param events data frame with columns \emph{tBeg} (event begins) and
#'   \emph{tEnd} (event ends)
#' @param signalWidth see description in \code{\link{hsEvents}}
#' @param timeUnit time unit of event duration and event pauses
#' @param pause if TRUE, pauses before and after the events are calculated
#' @param timeDifferences if time differences have been calculated beforehand,
#'   these may be given here (in seconds)
#' @export
toEvents <- function(
  events, signalWidth = .getSignalWidth(events, default = NA), timeUnit = "s",
  pause = TRUE, timeDifferences = NULL
)
{
  .checkEvents(events)
  
  # if there is a column dur, remove it first and give a warning
  events <- .removeAndWarnIfColumnExists(events, "dur")
  
  events <- cbind(
    events, 
    dur = eventDuration(events$tBeg, events$tEnd, signalWidth)
  )
  
  # Append columns pBefore, pEnd indicating pauses before and after 
  # event if requested
  if (isTRUE(pause)) {
    
    events <- .removeAndWarnIfColumnExists(events, "pBefore")
    events <- .removeAndWarnIfColumnExists(events, "pAfter")
    
    events <- cbind(
      events, 
      eventPauses(events, signalWidth, timeDifferences)
    )
  } 
  
  ## Set time unit as attribute of result data frame
  attr(events, "tUnit") <- "s"
  
  ## Convert durations to requested time unit if time unit does not equal "s"
  if (timeUnit != "s") {
    events <- hsEventsToUnit(events, timeUnit)
  }
  
  events
}

# .removeAndWarnIfColumnExists -------------------------------------------------

.removeAndWarnIfColumnExists <- function(events, columnName)
{
  if (columnName %in% names(events)) {
    
    events <- kwb.utils::removeColumns(events, columnName)
    
    warning(sprintf("The existing column '%s' was recalculated!", columnName))
  }
  
  events
}

# .checkEvents -----------------------------------------------------------------

.checkEvents <- function(events, requiredColumns = .eventColumns(...), ...)
{  
  if (!is.data.frame(events)) {
    stop("events is expected to be a data frame!")
  }
  
  kwb.utils::checkForMissingColumns(events, requiredColumns)
}

# .eventColumns ----------------------------------------------------------------

.eventColumns <- function(useIndex = FALSE, duration = FALSE)
{
  requiredColumns <- if (isTRUE(useIndex)) {
    c("iBeg", "iEnd")
  }
  else {
    c("tBeg", "tEnd")
  }
  
  if (isTRUE(duration)) {
    
    requiredColumns <- c(requiredColumns, "dur")
  }

  requiredColumns
}

# eventPauses ------------------------------------------------------------------

#' Pauses between Events in Seconds
#' 
#' @param events event information as returned by \code{\link{hsEvents}}
#' @param signalWidth see description in \code{\link{hsEvents}}
#' @param timeDifferences if time differences have been calculated beforehand,
#'   these may be given here (in seconds)
#' @return data frame with columns \emph{pBefore}, \emph{pEnd} with the pauses 
#'   between the given \emph{events}, in seconds
#' @export
eventPauses <- function(
  events, signalWidth = .getSignalWidth(events), timeDifferences = NULL
)
{ 
  .checkEvents(events)
  
  if (is.null(timeDifferences)) {
    
    # Remove first element from the event begins and the last element from the
    # event ends and calculate the differences
    n <- nrow(events)    
    timeDifferences <- as.double(events$tBeg[-1]) - as.double(events$tEnd[-n])
  }  
  
  timeDifferencesToPauses(timeDifferences, signalWidth)
}

# timeDifferencesToPauses ------------------------------------------------------

#' Time Differences to Columns "pBefore" and "pAfter"
#' 
#' @param timeDifferences numeric vector representing time differences
#' @param signalWidth difference between two consecutive timesteps in the 
#'   original time series
#' @return data frame with columns \emph{pBefore}, \emph{pAfter}, containing the
#'   given \emph{timeDifferences}, shifted against each other by one row, i.e.
#'   the first element in column \emph{pBefore} and the last element in column 
#'   \emph{pAfter} will be NA.
#' @export
timeDifferencesToPauses <- function(timeDifferences, signalWidth = 0)
{
  data.frame(
    pBefore = c(NA, timeDifferences) - signalWidth, 
    pAfter  = c(timeDifferences, NA) - signalWidth
  )  
}

# eventsOnChange -------------------------------------------------------------

#' Changes in Value Vector to Events
#' 
#' Creates \dQuote{events} from vector \emph{x} of values based on changes in 
#' the value of consecutive elements in \emph{x}.
#' 
#' @param x vector containing elements to be grouped into \dQuote{events}
#' @param numberOnly if TRUE, only the number of \dQuote{events} is returned
#'   instead of a data frame containing first and last index of each
#'   \dQuote{event}.
#' @param include.value if TRUE and \emph{numberOnly} is FALSE, the returned
#'   data frame will contain a column \emph{value} containing the value that was
#'   found in each index section between \emph{iBeg} and \emph{iEnd}.
#' @return Per default (\emph{numberOnly} = FALSE) a data frame is returned with
#'   as many rows as \dQuote{events} were found in vector \emph{x}. As long as
#'   the value in \emph{x} does not change from one index to the next, it is 
#'   assumed to belong to the same event. If the value changes, a new event 
#'   begins. In the result data frame each event is represented by \emph{iBeg} 
#'   and \emph{iEnd} which are the indices of the first and last element, 
#'   respectively, in \emph{x} that build the event. If \emph{numberOnly} is 
#'   TRUE the number of \dQuote{events} is returned, that is one plus the number
#'   of changes in the value of \emph{x} from its first to its last element.
#' @seealso \code{\link{hsEvents}}
#' @export
#' @examples 
#' eventsOnChange(c(1,2,2,3,4,4,4,5))
#'   
#' # Ouput: list of five events, i.e. there are four changes of 
#' #        the value in the given vector.
#' #
#' #   iBeg iEnd
#' # 1    1    1
#' # 2    2    3
#' # 3    4    4
#' # 4    5    7
#' # 5    8    8
#'   
#' eventsOnChange(c(1, 2, 2, 3, 4, 4, 4, 5), numberOnly = TRUE) ## 5 (events)
#'   
eventsOnChange <- function(x, numberOnly = FALSE, include.value = FALSE) 
{  
  changes <- kwb.utils::findChanges(x)
  
  # If desired return the number of events only
  if (numberOnly) {
    return(nrow(changes))
  }
  
  changes <- kwb.utils::renameColumns(changes, list(
    starts_at = "iBeg", 
    ends_at = "iEnd"
  ))
  
  if (include.value) {
    return(changes)
  } 
  
  kwb.utils::removeColumns(changes, "value")
}

# hsGetEvent -------------------------------------------------------------------

#' Get Sub-Timeseries belonging to Event(s)
#' 
#' @param tSeries data frame representing time series with first column holding
#'   the timestamp
#' @param events event information as returned by \code{\link{hsEvents}}
#' @param evtnums vector of event numbers to be selected
#' @param useIndex if TRUE, \emph{tSeries} is filtered by comparing the real row
#'   number in \emph{tSeries} with the begin and end indices given in columns 
#'   \emph{iBeg} and \emph{iEnd} of \emph{events}. If FALSE, tSeries is filtered
#'   by comparing the timestamps in tSeries with the begin and end timestamps
#'   given in columns \emph{tBeg} and \emph{tEnd} of \emph{events}. Defaults to
#'   TRUE if \emph{events} contains columns \emph{iBeg} and \emph{iEnd}
#' @return rows of \emph{tSeries} belonging to the event numbers listed in 
#'   \emph{evtnums}
#' @export
hsGetEvent <- function(tSeries, events, evtnums, useIndex = FALSE)
{ 
  .checkEvents(events, requiredColumns = .eventColumns(useIndex = useIndex))
  
  ## Init result data frame
  res <- NULL
  
  ## Loop through numbers of events to be selected
  for (evtnum in evtnums) {
    
    # If row IDs are available then filter by row ID else by timestamp
    if (isTRUE(useIndex)) {
      
      rowinds <- events$iBeg[evtnum]:events$iEnd[evtnum]        
      
    } else {
      #tstamps <- tSeries[[1]]
      #rowinds <- (   (tstamps >= events$tBeg[evtnum]) 
      #             & (tstamps <= events$tEnd[evtnum]))      
      rowinds <- kwb.datetime::timestampIn(
        tSeries[[1]], events$tBeg[evtnum], events$tEnd[evtnum], TRUE, TRUE
      )
    }
    
    res <- rbind(res, tSeries[rowinds, , drop = FALSE])
  }
  
  res
}
KWB-R/kwb.event documentation built on June 14, 2022, 1:15 p.m.