Nothing
#' @title Daily statistics of dendrometer data
#'
#' @description
#' Computes daily statistics from high-frequency dendrometer time series.
#' For each day, it extracts the minimum and maximum values, their times
#' of occurrence, daily mean and median, daily amplitude, the signed lag
#' between the time of maximum and minimum, the day-to-day change in the
#' daily maximum, and a daily status indicating whether the day is growing,
#' shrinking, or stable relative to the previous day.
#'
#' @details
#' The function requires a data frame with a time column in the first column
#' and one or more dendrometer series in the following columns. The user
#' selects the series using \code{TreeNum}.
#'
#' The returned object has class \code{"daily_output"}, so it can be plotted
#' directly with \code{plot()}.
#'
#' The column \code{Max_diff} is computed as:
#' \deqn{Max\_diff_t = Max_t - Max_{t-1}}
#'
#' The column \code{Day_status} is derived from \code{Max_diff}:
#' \itemize{
#' \item \code{"growing"} if \code{Max_diff > 0}
#' \item \code{"shrinking"} if \code{Max_diff < 0}
#' \item \code{"stable"} if \code{Max_diff = 0}
#' }
#'
#' The first day has \code{NA} for \code{Max_diff} and \code{Day_status}.
#'
#' @references
#' King G, Fonti P, Nievergelt D, Büntgen U, Frank D (2013)
#' Climatic drivers of hourly to yearly tree radius variations along a 6°C natural warming gradient.
#' \emph{Agricultural and Forest Meteorology} 168:36–46.
#' \doi{10.1016/j.agrformet.2012.08.002}
#'
#' @param df A data frame with the first column containing date-time stamps
#' convertible to \code{POSIXct} and subsequent columns containing
#' dendrometer measurements.
#' @param TreeNum Integer. The index of the tree (column) to analyze.
#' \code{TreeNum = 1} refers to the first dendrometer column after the
#' time column.
#'
#' @return A tibble of class \code{"daily_output"} containing:
#' \describe{
#' \item{DATE}{Calendar date.}
#' \item{Min}{Daily minimum value.}
#' \item{Time_min}{Time of day of minimum value.}
#' \item{Max}{Daily maximum value.}
#' \item{Time_max}{Time of day of maximum value.}
#' \item{mean}{Daily mean value.}
#' \item{median}{Daily median value.}
#' \item{amplitude}{Daily amplitude = Max - Min.}
#' \item{Time_min_h}{Time of minimum expressed in decimal hours.}
#' \item{Time_max_h}{Time of maximum expressed in decimal hours.}
#' \item{lag_h}{Signed difference in hours: Time_max_h - Time_min_h.}
#' \item{Remarks}{\code{"*"} if \code{Time_max > Time_min}, otherwise \code{""}.}
#' \item{Max_diff}{Difference between today's maximum and the previous day's maximum.}
#' \item{Day_status}{\code{"growing"}, \code{"shrinking"}, \code{"stable"}, or \code{NA}.}
#' }
#'
#' @note
#' The object returned by \code{daily.data()} can be plotted using
#' \code{plot()} because it is assigned class \code{"daily_output"}.
#'
#' @importFrom lubridate ymd_hms
#' @importFrom dplyr mutate group_by summarise rename lag case_when if_else %>%
#' @importFrom tibble tibble
#' @importFrom stats median
#'
#' @examples
#' \donttest{
#' data(nepa17)
#' daily_stats <- daily.data(df = nepa17[1:1000, ], TreeNum = 1)
#' head(daily_stats, 10)
#' }
#'
#' @export
daily.data <- function(df, TreeNum) {
if (TreeNum > ncol(df) - 1) {
stop("The tree number is not valid for provided dataset.")
}
TIME <- DATE <- dm <- time <- Time_min <- Time_max <- Time_max_h <- Time_min_h <- Max <- NULL
tn <- TreeNum + 1
if (!inherits(df[[1]], "Date") && !inherits(df[[1]], "POSIXct")) {
df[[1]] <- lubridate::ymd_hms(df[[1]])
}
time_to_hours <- function(x) {
x <- as.character(x)
bad <- is.na(x) | x == ""
hh <- suppressWarnings(as.numeric(substr(x, 1, 2)))
mm <- suppressWarnings(as.numeric(substr(x, 4, 5)))
ss <- suppressWarnings(as.numeric(substr(x, 7, 8)))
out <- hh + mm / 60 + ss / 3600
out[bad] <- NA_real_
out
}
dat <- tibble::tibble(
TIME = as.POSIXct(df[[1]]),
dm = df[[tn]]
) %>%
dplyr::mutate(
DATE = as.Date(TIME),
time = format(TIME, "%H:%M:%S")
)
result <- dat %>%
dplyr::group_by(DATE) %>%
dplyr::summarise(
Min = if (all(is.na(dm))) NA_real_ else min(dm, na.rm = TRUE),
Time_min = if (all(is.na(dm))) {
NA_character_
} else {
time[which.min(replace(dm, is.na(dm), Inf))][1]
},
Max = if (all(is.na(dm))) NA_real_ else max(dm, na.rm = TRUE),
Time_max = if (all(is.na(dm))) {
NA_character_
} else {
time[which.max(replace(dm, is.na(dm), -Inf))][1]
},
mean = if (all(is.na(dm))) NA_real_ else mean(dm, na.rm = TRUE),
median = if (all(is.na(dm))) NA_real_ else stats::median(dm, na.rm = TRUE),
amplitude = if (all(is.na(dm))) NA_real_ else max(dm, na.rm = TRUE) - min(dm, na.rm = TRUE),
.groups = "drop"
) %>%
dplyr::mutate(
Time_min_h = time_to_hours(Time_min),
Time_max_h = time_to_hours(Time_max),
lag_h = Time_max_h - Time_min_h,
Remarks = dplyr::if_else(
!is.na(Time_max_h) & !is.na(Time_min_h) & Time_max_h > Time_min_h,
"*", ""
),
Max_diff = Max - dplyr::lag(Max),
Day_status = dplyr::case_when(
is.na(Max_diff) ~ NA_character_,
Max_diff > 0 ~ "growing",
Max_diff < 0 ~ "shrinking",
TRUE ~ "stable"
)
)
attr(result, "tree_num") <- TreeNum
attr(result, "tree_col") <- names(df)[tn]
class(result) <- c("daily_output", class(result))
result
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.