diff --git a/NAMESPACE b/NAMESPACE index cb04785a..fad615cb 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -9,7 +9,9 @@ export(direction_group) export(direction_polarization) export(direction_step) export(direction_to_centroid) +export(direction_to_leader) export(distance_to_centroid) +export(distance_to_leader) export(dyad_id) export(edge_dist) export(edge_nn) diff --git a/R/direction_group.R b/R/direction_group.R index 93043c8c..b2391b9c 100644 --- a/R/direction_group.R +++ b/R/direction_group.R @@ -16,8 +16,8 @@ #' direction column is expected in units of radians and the mean calculated with #' [CircStats::circ.mean()]. #' -#' @param DT input data.table with distance column generated by -#' \code{distance_step} and group column generated with \code{group_pts} +#' @param DT input data.table with direction column generated by +#' \code{direction_step} and group column generated with \code{group_pts} #' @param direction character string of direction column name, default #' "direction" #' @param group character string of group column name, default "group" diff --git a/R/direction_to_centroid.R b/R/direction_to_centroid.R index 6788d959..75f9eabc 100644 --- a/R/direction_to_centroid.R +++ b/R/direction_to_centroid.R @@ -18,6 +18,7 @@ #' expect the names of columns in \code{DT} which correspond to the X and Y #' coordinates and group columns. #' +#' @inheritParams distance_to_centroid #' @inheritParams group_pts #' #' @return \code{direction_to_centroid} returns the input \code{DT} appended diff --git a/R/direction_to_leader.R b/R/direction_to_leader.R new file mode 100644 index 00000000..37431ff0 --- /dev/null +++ b/R/direction_to_leader.R @@ -0,0 +1,174 @@ +#' Direction to group leader +#' +#' \code{direction_to_leader} calculates the direction to the leader of each +#' spatiotemporal group. The function accepts a \code{data.table} with +#' relocation data appended with a \code{rank_position_group_direction} column +#' indicating the ranked position along the group direction generated with +#' \code{leader_direction_group(return_rank = TRUE)}. Relocation data should be +#' in planar coordinates provided in two columns representing the X and Y +#' coordinates. +#' +#' The \code{DT} must be a \code{data.table}. If your data is a +#' \code{data.frame}, you can convert it by reference using +#' \code{\link[data.table:setDT]{data.table::setDT}} or by reassigning using +#' \code{\link[data.table:data.table]{data.table::data.table}}. +#' +#' This function expects a \code{rank_position_group_direction} column +#' generated with \code{leader_direction_group(return_rank = TRUE)}, +#' a \code{group} column generated with the +#' \code{group_pts} function. The \code{coords} and \code{group} arguments +#' expect the names of columns in \code{DT} which correspond to the X and Y +#' coordinates and group columns. +#' +#' @inheritParams distance_to_leader +#' +#' @return \code{direction_to_leader} returns the input \code{DT} appended with +#' a \code{direction_leader} column indicating the direction to the group leader. +#' +#' A message is returned when the \code{direction_leader} column is already exist in the input \code{DT} +#' because it will be overwritten. +#' +#' @export +#' @family Direction functions +#' @seealso [distance_to_leader], [leader_direction_group], [group_pts] +#' @references +#' +#' See examples of using direction to leader and position within group: +#' * +#' * +#' * +#' +#' @examples +#' # Load data.table +#' library(data.table) +#' \dontshow{data.table::setDTthreads(1)} +#' +#' # Read example data +#' DT <- fread(system.file("extdata", "DT.csv", package = "spatsoc")) +#' +#' # (Subset example data to reduce example run time) +#' DT <- DT[year(datetime) == 2016] +#' +#' # Cast the character column to POSIXct +#' DT[, datetime := as.POSIXct(datetime, tz = 'UTC')] +#' +#' # Temporal grouping +#' group_times(DT, datetime = 'datetime', threshold = '20 minutes') +#' +#' # Spatial grouping with timegroup +#' group_pts(DT, threshold = 50, id = 'ID', +#' coords = c('X', 'Y'), timegroup = 'timegroup') +#' +#' # Calculate direction at each step +#' direction_step( +#' DT = DT, +#' id = 'ID', +#' coords = c('X', 'Y'), +#' projection = 32736 +#' ) +#' +#' # Calculate group centroid +#' centroid_group(DT, coords = c('X', 'Y')) +#' +#' # Calculate group direction +#' direction_group(DT) +#' +#' # Calculate leader in terms of position along group direction +#' leader_direction_group( +#' DT, +#' coords = c('X', 'Y'), +#' return_rank = TRUE +#' ) +#' +#' # Calculate direction to leader +#' direction_to_leader(DT, coords = c('X', 'Y')) +direction_to_leader <- function( + DT = NULL, + coords = NULL, + group = 'group') { + # Due to NSE notes + direction_leader <- rank_position_group_direction <- has_leader <- + zzz_N_by_group <- . <- NULL + + if (is.null(DT)) { + stop('input DT required') + } + + if (is.null(group)) { + stop('group column name required') + } + + if (length(coords) != 2) { + stop('coords requires a vector of column names for coordinates X and Y') + } + + if (!group %in% colnames(DT)) { + stop('group column not present in input DT, did you run group_pts?') + } + + check_cols <- c(coords, group) + + if (any(!(check_cols %in% colnames(DT)))) { + stop(paste0( + as.character(paste(setdiff( + check_cols, + colnames(DT) + ), collapse = ', ')), + ' field(s) provided are not present in input DT' + )) + } + + if (any(!(DT[, vapply(.SD, is.numeric, TRUE), .SDcols = coords]))) { + stop('coords must be numeric') + } + + leader_col <- 'rank_position_group_direction' + + if (!leader_col %in% colnames(DT)) { + stop(paste0( + leader_col, + ' column not present in input DT, ', + 'did you run leader_direction_group(return_rank = TRUE)?')) + } + + if (!is.numeric(DT[[leader_col]])) { + stop(paste0(leader_col, ' column must be numeric')) + } + + out_col <- 'direction_leader' + if (out_col %in% colnames(DT)) { + message( + paste0(out_col, ' column will be overwritten by this function') + ) + data.table::set(DT, j = out_col, value = NULL) + } + + check_has_leader <- DT[, .( + has_leader = any(rank_position_group_direction == 1)), + by = c(group)][!(has_leader)] + + if (check_has_leader[, .N > 0]) { + warning( + 'groups found missing leader (rank_position_group_direction == 1): \n', + check_has_leader[, paste(group, collapse = ', ')] + ) + } + + zzz_leader_coords <- c('zzz_leader_xcol', 'zzz_leader_ycol') + DT[, c(zzz_leader_coords) := + .SD[which(rank_position_group_direction == 1)], + .SDcols = c(coords), + by = c(group)] + + DT[!group %in% check_has_leader$group, direction_leader := fifelse( + .SD[[1]] == .SD[[3]] & + .SD[[2]] == .SD[[4]], + NaN, + atan2(.SD[[4]] - .SD[[2]], (.SD[[3]] - .SD[[1]])) + ), + .SDcols = c(coords, zzz_leader_coords)] + + data.table::set(DT, j = zzz_leader_coords, value = NULL) + + return(DT[]) +} diff --git a/R/distance_to_centroid.R b/R/distance_to_centroid.R index b709b5a2..016dbda1 100644 --- a/R/distance_to_centroid.R +++ b/R/distance_to_centroid.R @@ -22,12 +22,14 @@ #' \code{data.table::frank}, see details at #' \code{\link[data.table:frank]{?data.table::frank}}. #' -#' @inheritParams group_pts +#' @param DT input data.table with centroid columns generated by eg. +#' \code{centroid_group} #' @param group group column name, generated by \code{group_pts}, default #' 'group' #' @param return_rank boolean if rank distance should also be returned, default #' FALSE #' @param ties.method see \code{\link[data.table:frank]{?data.table::frank}} +#' @inheritParams group_pts #' #' @return \code{distance_to_centroid} returns the input \code{DT} appended with #' a \code{distance_centroid} column indicating the distance to group centroid diff --git a/R/distance_to_leader.R b/R/distance_to_leader.R new file mode 100644 index 00000000..6bbab8dd --- /dev/null +++ b/R/distance_to_leader.R @@ -0,0 +1,177 @@ +#' Distance to group leader +#' +#' \code{distance_to_leader} calculates the distance to the leader of each +#' spatiotemporal group. The function accepts a \code{data.table} with +#' relocation data appended with a \code{rank_position_group_direction} column +#' indicating the ranked position along the group direction generated with +#' \code{leader_direction_group(return_rank = TRUE)}. Relocation data should be +#' in planar coordinates provided in two columns representing the X and Y +#' coordinates. +#' +#' The \code{DT} must be a \code{data.table}. If your data is a +#' \code{data.frame}, you can convert it by reference using +#' \code{\link[data.table:setDT]{data.table::setDT}} or by reassigning using +#' \code{\link[data.table:data.table]{data.table::data.table}}. +#' +#' This function expects a \code{rank_position_group_direction} column +#' generated with \code{leader_direction_group(return_rank = TRUE)}, +#' a \code{group} column generated with the +#' \code{group_pts} function. The \code{coords} and \code{group} arguments +#' expect the names of columns in \code{DT} which correspond to the X and Y +#' coordinates and group columns. +#' +#' @param DT input data.table with 'rank_position_group_direction' column +#' generated by \code{leader_direction_group} and group column generated by +#' \code{group_pts} +#' @inheritParams leader_direction_group +#' +#' @return \code{distance_to_leader} returns the input \code{DT} appended with +#' a \code{distance_leader} column indicating the distance to the group leader. +#' +#' A message is returned when the \code{distance_leader} column is already exist in the input \code{DT} +#' because it will be overwritten. +#' +#' @export +#' @family Distance functions +#' @seealso [direction_to_leader], [leader_direction_group], [group_pts] +#' @references +#' +#' See examples of using distance to leader and position within group: +#' * +#' * +#' * +#' +#' @examples +#' # Load data.table +#' library(data.table) +#' \dontshow{data.table::setDTthreads(1)} +#' +#' # Read example data +#' DT <- fread(system.file("extdata", "DT.csv", package = "spatsoc")) +#' +#' # Cast the character column to POSIXct +#' DT[, datetime := as.POSIXct(datetime, tz = 'UTC')] +#' +#' # (Subset example data to reduce example run time) +#' DT <- DT[year(datetime) == 2016] +#' +#' # Temporal grouping +#' group_times(DT, datetime = 'datetime', threshold = '20 minutes') +#' +#' # Spatial grouping with timegroup +#' group_pts(DT, threshold = 50, id = 'ID', +#' coords = c('X', 'Y'), timegroup = 'timegroup') +#' +#' # Calculate direction at each step +#' direction_step( +#' DT = DT, +#' id = 'ID', +#' coords = c('X', 'Y'), +#' projection = 32736 +#' ) +#' +#' # Calculate group centroid +#' centroid_group(DT, coords = c('X', 'Y')) +#' +#' # Calculate group direction +#' direction_group(DT) +#' +#' # Calculate leader in terms of position along group direction +#' leader_direction_group( +#' DT, +#' coords = c('X', 'Y'), +#' return_rank = TRUE +#' ) +#' +#' # Calculate distance to leader +#' distance_to_leader(DT, coords = c('X', 'Y')) +distance_to_leader <- function( + DT = NULL, + coords = NULL, + group = 'group') { + # Due to NSE notes + distance_leader <- zzz_N_by_group <- rank_position_group_direction <- + has_leader <- . <- NULL + + if (is.null(DT)) { + stop('input DT required') + } + + if (is.null(group)) { + stop('group column name required') + } + + if (length(coords) != 2) { + stop('coords requires a vector of column names for coordinates X and Y') + } + + if (!group %in% colnames(DT)) { + stop('group column not present in input DT, did you run group_pts?') + } + + check_cols <- c(coords, group) + + if (any(!(check_cols %in% colnames(DT)))) { + stop(paste0( + as.character(paste(setdiff( + check_cols, + colnames(DT) + ), collapse = ', ')), + ' field(s) provided are not present in input DT' + )) + } + + if (any(!(DT[, vapply(.SD, is.numeric, TRUE), .SDcols = coords]))) { + stop('coords must be numeric') + } + + leader_col <- 'rank_position_group_direction' + + if (!leader_col %in% colnames(DT)) { + stop(paste0( + leader_col, + ' column not present in input DT, ', + 'did you run leader_direction_group(return_rank = TRUE)?')) + } + + if (!is.numeric(DT[[leader_col]])) { + stop(paste0(leader_col, ' column must be numeric')) + } + + + out_col <- 'distance_leader' + if (out_col %in% colnames(DT)) { + message( + paste0(out_col, ' column will be overwritten by this function') + ) + data.table::set(DT, j = out_col, value = NULL) + } + + DT[, zzz_N_by_group := .N, by = c(group)] + + check_has_leader <- DT[, .( + has_leader = any(rank_position_group_direction == 1)), + by = c(group)][!(has_leader)] + + if (check_has_leader[, .N > 0]) { + warning( + 'groups found missing leader (rank_position_group_direction == 1): \n', + check_has_leader[, paste(group, collapse = ', ')] + ) + } + + DT[!group %in% check_has_leader$group, + c(out_col) := fifelse( + zzz_N_by_group > 1, + as.matrix( + stats::dist(cbind(.SD[[1]], .SD[[2]])) + )[, which(.SD[[3]] == 1)], + 0 + ), + .SDcols = c(coords, 'rank_position_group_direction'), + by = c(group)] + + data.table::set(DT, j = 'zzz_N_by_group', value = NULL) + + return(DT[]) +} diff --git a/R/leader_direction_group.R b/R/leader_direction_group.R index 1bcb52b4..074df0db 100644 --- a/R/leader_direction_group.R +++ b/R/leader_direction_group.R @@ -30,17 +30,19 @@ #' #' @return \code{leader_direction_group} returns the input \code{DT} appended #' with a \code{position_group_direction} column indicating the position along -#' the group direction in the units of the projection and, optionally, a -#' \code{rank_position_group_direction} column indicating the -#' within group rank position along the group dirtection \code{return_rank = -#' TRUE}). +#' the group direction in the units of the projection and, optionally when +#' \code{return_rank = TRUE}, a \code{rank_position_group_direction} column +#' indicating the the ranked position along the group direction. #' #' A message is returned when \code{position_group_direction} or #' \code{rank_position_group_direction} columns already exist in the input #' \code{DT}, because they will be overwritten. #' -#' @inheritParams direction_group +#' @param DT input data.table with group direction columns generated by +#' \code{direction_group} and centroid columns generated by +#' \code{centroid_group} #' @inheritParams distance_to_centroid +#' @inheritParams group_pts #' @param group_direction group_direction column name generated using #' \code{direction_group}, default 'group_direction' #' @@ -63,6 +65,9 @@ #' # Read example data #' DT <- fread(system.file("extdata", "DT.csv", package = "spatsoc")) #' +#' # (Subset example data to reduce example run time) +#' DT <- DT[year(datetime) == 2016] +#' #' # Cast the character column to POSIXct #' DT[, datetime := as.POSIXct(datetime, tz = 'UTC')] #' @@ -93,7 +98,7 @@ leader_direction_group <- function( DT = NULL, group_direction = 'group_direction', coords = NULL, - group = NULL, + group = 'group', return_rank = FALSE, ties.method = 'average') { # Due to NSE notes diff --git a/man/direction_group.Rd b/man/direction_group.Rd index 9ee8f334..560d441b 100644 --- a/man/direction_group.Rd +++ b/man/direction_group.Rd @@ -7,8 +7,8 @@ direction_group(DT, direction = "direction", group = "group") } \arguments{ -\item{DT}{input data.table with distance column generated by -\code{distance_step} and group column generated with \code{group_pts}} +\item{DT}{input data.table with direction column generated by +\code{direction_step} and group column generated with \code{group_pts}} \item{direction}{character string of direction column name, default "direction"} @@ -87,6 +87,7 @@ See examples of using mean group direction: Other Direction functions: \code{\link{direction_polarization}()}, -\code{\link{direction_step}()} +\code{\link{direction_step}()}, +\code{\link{direction_to_leader}()} } \concept{Direction functions} diff --git a/man/direction_polarization.Rd b/man/direction_polarization.Rd index 51312ebb..4db6d66e 100644 --- a/man/direction_polarization.Rd +++ b/man/direction_polarization.Rd @@ -7,8 +7,8 @@ direction_polarization(DT, direction = "direction", group = "group") } \arguments{ -\item{DT}{input data.table with distance column generated by -\code{distance_step} and group column generated with \code{group_pts}} +\item{DT}{input data.table with direction column generated by +\code{direction_step} and group column generated with \code{group_pts}} \item{direction}{character string of direction column name, default "direction"} @@ -87,6 +87,7 @@ See examples of using polarization: Other Direction functions: \code{\link{direction_group}()}, -\code{\link{direction_step}()} +\code{\link{direction_step}()}, +\code{\link{direction_to_leader}()} } \concept{Direction functions} diff --git a/man/direction_step.Rd b/man/direction_step.Rd index 0ec38ee2..aaa64f30 100644 --- a/man/direction_step.Rd +++ b/man/direction_step.Rd @@ -100,6 +100,7 @@ direction_step( Other Direction functions: \code{\link{direction_group}()}, -\code{\link{direction_polarization}()} +\code{\link{direction_polarization}()}, +\code{\link{direction_to_leader}()} } \concept{Direction functions} diff --git a/man/direction_to_centroid.Rd b/man/direction_to_centroid.Rd index c459ea37..312d8b28 100644 --- a/man/direction_to_centroid.Rd +++ b/man/direction_to_centroid.Rd @@ -7,7 +7,8 @@ direction_to_centroid(DT = NULL, coords = NULL) } \arguments{ -\item{DT}{input data.table} +\item{DT}{input data.table with centroid columns generated by eg. +\code{centroid_group}} \item{coords}{character vector of X coordinate and Y coordinate column names. Note: the order is assumed X followed by Y column names.} @@ -75,6 +76,7 @@ See example of using direction to group centroid: \link{centroid_group}, \link{group_pts} Other Distance functions: -\code{\link{distance_to_centroid}()} +\code{\link{distance_to_centroid}()}, +\code{\link{distance_to_leader}()} } \concept{Distance functions} diff --git a/man/direction_to_leader.Rd b/man/direction_to_leader.Rd new file mode 100644 index 00000000..b200361f --- /dev/null +++ b/man/direction_to_leader.Rd @@ -0,0 +1,110 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/direction_to_leader.R +\name{direction_to_leader} +\alias{direction_to_leader} +\title{Direction to group leader} +\usage{ +direction_to_leader(DT = NULL, coords = NULL, group = "group") +} +\arguments{ +\item{DT}{input data.table with 'rank_position_group_direction' column +generated by \code{leader_direction_group} and group column generated by +\code{group_pts}} + +\item{coords}{character vector of X coordinate and Y coordinate column names. +Note: the order is assumed X followed by Y column names.} + +\item{group}{group column name, generated by \code{group_pts}, default +'group'} +} +\value{ +\code{direction_to_leader} returns the input \code{DT} appended with +a \code{direction_leader} column indicating the direction to the group leader. + +A message is returned when the \code{direction_leader} column is already exist in the input \code{DT} +because it will be overwritten. +} +\description{ +\code{direction_to_leader} calculates the direction to the leader of each +spatiotemporal group. The function accepts a \code{data.table} with +relocation data appended with a \code{rank_position_group_direction} column +indicating the ranked position along the group direction generated with +\code{leader_direction_group(return_rank = TRUE)}. Relocation data should be +in planar coordinates provided in two columns representing the X and Y +coordinates. +} +\details{ +The \code{DT} must be a \code{data.table}. If your data is a +\code{data.frame}, you can convert it by reference using +\code{\link[data.table:setDT]{data.table::setDT}} or by reassigning using +\code{\link[data.table:data.table]{data.table::data.table}}. + +This function expects a \code{rank_position_group_direction} column +generated with \code{leader_direction_group(return_rank = TRUE)}, +a \code{group} column generated with the +\code{group_pts} function. The \code{coords} and \code{group} arguments +expect the names of columns in \code{DT} which correspond to the X and Y +coordinates and group columns. +} +\examples{ +# Load data.table +library(data.table) +\dontshow{data.table::setDTthreads(1)} + +# Read example data +DT <- fread(system.file("extdata", "DT.csv", package = "spatsoc")) + +# (Subset example data to reduce example run time) +DT <- DT[year(datetime) == 2016] + +# Cast the character column to POSIXct +DT[, datetime := as.POSIXct(datetime, tz = 'UTC')] + +# Temporal grouping +group_times(DT, datetime = 'datetime', threshold = '20 minutes') + +# Spatial grouping with timegroup +group_pts(DT, threshold = 50, id = 'ID', + coords = c('X', 'Y'), timegroup = 'timegroup') + +# Calculate direction at each step +direction_step( + DT = DT, + id = 'ID', + coords = c('X', 'Y'), + projection = 32736 +) + +# Calculate group centroid +centroid_group(DT, coords = c('X', 'Y')) + +# Calculate group direction +direction_group(DT) + +# Calculate leader in terms of position along group direction +leader_direction_group( + DT, + coords = c('X', 'Y'), + return_rank = TRUE +) + +# Calculate direction to leader +direction_to_leader(DT, coords = c('X', 'Y')) +} +\references{ +See examples of using direction to leader and position within group: +\itemize{ +\item \url{https://doi.org/10.1016/j.anbehav.2023.09.009} +\item \url{https://doi.org/10.1016/j.beproc.2013.10.007} +\item \url{https://doi.org/10.1371/journal.pone.0036567} +} +} +\seealso{ +\link{distance_to_leader}, \link{leader_direction_group}, \link{group_pts} + +Other Direction functions: +\code{\link{direction_group}()}, +\code{\link{direction_polarization}()}, +\code{\link{direction_step}()} +} +\concept{Direction functions} diff --git a/man/distance_to_centroid.Rd b/man/distance_to_centroid.Rd index f27b5862..2f471090 100644 --- a/man/distance_to_centroid.Rd +++ b/man/distance_to_centroid.Rd @@ -13,7 +13,8 @@ distance_to_centroid( ) } \arguments{ -\item{DT}{input data.table} +\item{DT}{input data.table with centroid columns generated by eg. +\code{centroid_group}} \item{coords}{character vector of X coordinate and Y coordinate column names. Note: the order is assumed X followed by Y column names.} @@ -102,6 +103,7 @@ See examples of using distance to group centroid: \link{centroid_group}, \link{group_pts} Other Distance functions: -\code{\link{direction_to_centroid}()} +\code{\link{direction_to_centroid}()}, +\code{\link{distance_to_leader}()} } \concept{Distance functions} diff --git a/man/distance_to_leader.Rd b/man/distance_to_leader.Rd new file mode 100644 index 00000000..ce7d5b05 --- /dev/null +++ b/man/distance_to_leader.Rd @@ -0,0 +1,109 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/distance_to_leader.R +\name{distance_to_leader} +\alias{distance_to_leader} +\title{Distance to group leader} +\usage{ +distance_to_leader(DT = NULL, coords = NULL, group = "group") +} +\arguments{ +\item{DT}{input data.table with 'rank_position_group_direction' column +generated by \code{leader_direction_group} and group column generated by +\code{group_pts}} + +\item{coords}{character vector of X coordinate and Y coordinate column names. +Note: the order is assumed X followed by Y column names.} + +\item{group}{group column name, generated by \code{group_pts}, default +'group'} +} +\value{ +\code{distance_to_leader} returns the input \code{DT} appended with +a \code{distance_leader} column indicating the distance to the group leader. + +A message is returned when the \code{distance_leader} column is already exist in the input \code{DT} +because it will be overwritten. +} +\description{ +\code{distance_to_leader} calculates the distance to the leader of each +spatiotemporal group. The function accepts a \code{data.table} with +relocation data appended with a \code{rank_position_group_direction} column +indicating the ranked position along the group direction generated with +\code{leader_direction_group(return_rank = TRUE)}. Relocation data should be +in planar coordinates provided in two columns representing the X and Y +coordinates. +} +\details{ +The \code{DT} must be a \code{data.table}. If your data is a +\code{data.frame}, you can convert it by reference using +\code{\link[data.table:setDT]{data.table::setDT}} or by reassigning using +\code{\link[data.table:data.table]{data.table::data.table}}. + +This function expects a \code{rank_position_group_direction} column +generated with \code{leader_direction_group(return_rank = TRUE)}, +a \code{group} column generated with the +\code{group_pts} function. The \code{coords} and \code{group} arguments +expect the names of columns in \code{DT} which correspond to the X and Y +coordinates and group columns. +} +\examples{ +# Load data.table +library(data.table) +\dontshow{data.table::setDTthreads(1)} + +# Read example data +DT <- fread(system.file("extdata", "DT.csv", package = "spatsoc")) + +# Cast the character column to POSIXct +DT[, datetime := as.POSIXct(datetime, tz = 'UTC')] + +# (Subset example data to reduce example run time) +DT <- DT[year(datetime) == 2016] + +# Temporal grouping +group_times(DT, datetime = 'datetime', threshold = '20 minutes') + +# Spatial grouping with timegroup +group_pts(DT, threshold = 50, id = 'ID', + coords = c('X', 'Y'), timegroup = 'timegroup') + +# Calculate direction at each step +direction_step( + DT = DT, + id = 'ID', + coords = c('X', 'Y'), + projection = 32736 +) + +# Calculate group centroid +centroid_group(DT, coords = c('X', 'Y')) + +# Calculate group direction +direction_group(DT) + +# Calculate leader in terms of position along group direction +leader_direction_group( + DT, + coords = c('X', 'Y'), + return_rank = TRUE +) + +# Calculate distance to leader +distance_to_leader(DT, coords = c('X', 'Y')) +} +\references{ +See examples of using distance to leader and position within group: +\itemize{ +\item \url{https://doi.org/10.1111/jfb.15315} +\item \url{https://doi.org/10.1098/rspb.2017.2629} +\item \url{https://doi.org/10.1016/j.anbehav.2023.09.009} +} +} +\seealso{ +\link{direction_to_leader}, \link{leader_direction_group}, \link{group_pts} + +Other Distance functions: +\code{\link{direction_to_centroid}()}, +\code{\link{distance_to_centroid}()} +} +\concept{Distance functions} diff --git a/man/leader_direction_group.Rd b/man/leader_direction_group.Rd index b3835cbf..e9ee6432 100644 --- a/man/leader_direction_group.Rd +++ b/man/leader_direction_group.Rd @@ -8,14 +8,15 @@ leader_direction_group( DT = NULL, group_direction = "group_direction", coords = NULL, - group = NULL, + group = "group", return_rank = FALSE, ties.method = "average" ) } \arguments{ -\item{DT}{input data.table with distance column generated by -\code{distance_step} and group column generated with \code{group_pts}} +\item{DT}{input data.table with group direction columns generated by +\code{direction_group} and centroid columns generated by +\code{centroid_group}} \item{group_direction}{group_direction column name generated using \code{direction_group}, default 'group_direction'} @@ -23,7 +24,8 @@ leader_direction_group( \item{coords}{character vector of X coordinate and Y coordinate column names. Note: the order is assumed X followed by Y column names.} -\item{group}{character string of group column name, default "group"} +\item{group}{group column name, generated by \code{group_pts}, default +'group'} \item{return_rank}{boolean if rank distance should also be returned, default FALSE} @@ -33,10 +35,9 @@ FALSE} \value{ \code{leader_direction_group} returns the input \code{DT} appended with a \code{position_group_direction} column indicating the position along -the group direction in the units of the projection and, optionally, a -\code{rank_position_group_direction} column indicating the -within group rank position along the group dirtection \code{return_rank = - TRUE}). +the group direction in the units of the projection and, optionally when +\code{return_rank = TRUE}, a \code{rank_position_group_direction} column +indicating the the ranked position along the group direction. A message is returned when \code{position_group_direction} or \code{rank_position_group_direction} columns already exist in the input @@ -80,6 +81,9 @@ library(data.table) # Read example data DT <- fread(system.file("extdata", "DT.csv", package = "spatsoc")) +# (Subset example data to reduce example run time) +DT <- DT[year(datetime) == 2016] + # Cast the character column to POSIXct DT[, datetime := as.POSIXct(datetime, tz = 'UTC')] diff --git a/tests/testthat/test-direction-to-leader.R b/tests/testthat/test-direction-to-leader.R new file mode 100644 index 00000000..9dced628 --- /dev/null +++ b/tests/testthat/test-direction-to-leader.R @@ -0,0 +1,136 @@ +# Test direction_to_leader +context('test direction_to_leader') + +library(spatsoc) + +DT <- fread('../testdata/DT.csv') +id <- 'ID' +datetime <- 'datetime' +timethreshold <- '20 minutes' +threshold <- 50 +coords <- c('X', 'Y') +timegroup <- 'timegroup' +group <- 'group' +projection <- 32736 + +DT[, datetime := as.POSIXct(datetime, tz = 'UTC')] +group_times(DT, datetime = datetime, timethreshold) +group_pts(DT, threshold = threshold, id = id, + coords = coords, timegroup = timegroup) +centroid_group(DT, coords = coords, group = group, na.rm = TRUE) +direction_step(DT = DT, id = id, coords = coords, projection = projection) +direction_group(DT) +leader_direction_group(DT, coords = coords, group = group, return_rank = TRUE) + +# Removing group with missing leader +DT <- copy(DT)[group != 868] + +clean_DT <- copy(DT) + +test_that('DT is required', { + expect_error(direction_to_leader(DT = NULL)) +}) + +test_that('arguments required, otherwise error detected', { + expect_error(direction_to_leader(DT, coords = NULL, group = group), + 'coords req') + expect_error(direction_to_leader(DT, coords = coords, group = NULL), + 'group column name required') +}) + +test_that('column names must exist in DT', { + expect_error(direction_to_leader(DT, coords = rep('potato', 2), group = group), + 'potato field') + expect_error(direction_to_leader(DT, coords = coords, group = 'potato'), + 'group column') + copy_DT <- copy(DT) + setnames(copy_DT, 'rank_position_group_direction', 'potato') + expect_error(direction_to_leader(copy_DT, coords = coords, group = group), + 'did you run leader?') +}) + +test_that('coords are correctly provided or error detected', { + expect_error(direction_to_leader(DT, coords = c('X', NULL), group = group), + 'coords requires a vector') + copy_DT <- copy(DT)[, X := as.character(X)] + expect_error(direction_to_leader(copy_DT, coords = coords, group = group), + 'coords must be numeric') + copy_DT <- copy(DT)[, X := as.character(X)] + expect_error(direction_to_leader(copy_DT, coords = coords, + group = group), + 'coords must be numeric') + copy_DT <- copy(DT)[, rank_position_group_direction := NULL] + expect_error(direction_to_leader(copy_DT, coords = coords, + group = group)) +}) + +test_that('message when direction_leader column overwritten', { + copyDT <- copy(clean_DT)[, direction_leader := 1] + expect_message( + direction_to_leader(copyDT, coords = coords, group = group), + 'direction_leader column will be overwritten' + ) +}) + +test_that('no rows are added to the result DT', { + copyDT <- copy(clean_DT) + + expect_equal(nrow(copyDT), + nrow(direction_to_leader(copyDT, coords = coords, group = group))) +}) + +test_that('one column added to the result DT', { + copyDT <- copy(clean_DT) + + expect_equal(ncol(copyDT) + 1, + ncol(direction_to_leader(copyDT, coords = coords, group = group))) +}) + +test_that('column added to the result DT is a double', { + expect_type( + direction_to_leader(DT, coords = coords, group = group)$direction_leader, + 'double' + ) +}) + +test_that('zzz columns not added to the result', { + zzz_cols <- c('has_leader', 'zzz_leader_xcol', 'zzz_leader_ycol') + + expect_false( + any(zzz_cols %in% colnames(direction_to_leader(DT, coords = coords))) + ) +}) + +test_that('returns a data.table', { + expect_s3_class(direction_to_leader(DT, coords = coords, group = group), + 'data.table') +}) + +expect_DT <- data.table( + ID = c('A', 'B'), + X = c(0, 10), + Y = c(0, 0), + group_direction = rep(as_units(0, 'rad'), 2), + group = c(1, 1) +) +centroid_group(expect_DT, coords = coords) +leader_direction_group(expect_DT, coords = coords, + return_rank = TRUE, group = group) +direction_to_leader(expect_DT, coords = c('X', 'Y')) + +test_that('expected results for simple case', { + expect_lte( + expect_DT[, max(direction_leader, na.rm = TRUE)], + 10 + ) + + expect_equal( + expect_DT[is.na(direction_leader), .N], + 1 + ) + expect_equal( + expect_DT[is.na(direction_leader), ID], + 'B' + ) +}) + diff --git a/tests/testthat/test-distance-to-leader.R b/tests/testthat/test-distance-to-leader.R new file mode 100644 index 00000000..844b8b29 --- /dev/null +++ b/tests/testthat/test-distance-to-leader.R @@ -0,0 +1,139 @@ +# Test distance_to_leader +context('test distance_to_leader') + +library(spatsoc) + +DT <- fread('../testdata/DT.csv') +id <- 'ID' +datetime <- 'datetime' +timethreshold <- '20 minutes' +threshold <- 50 +coords <- c('X', 'Y') +timegroup <- 'timegroup' +group <- 'group' +projection <- 32736 + +DT[, datetime := as.POSIXct(datetime, tz = 'UTC')] +group_times(DT, datetime = datetime, timethreshold) +group_pts(DT, threshold = threshold, id = id, + coords = coords, timegroup = timegroup) +centroid_group(DT, coords = coords, group = group, na.rm = TRUE) +direction_step(DT = DT, id = id, coords = coords, projection = projection) +direction_group(DT) +leader_direction_group(DT, coords = coords, group = group, return_rank = TRUE) + +# Removing group with missing leader +DT <- copy(DT)[group != 868] + +clean_DT <- copy(DT) + +test_that('DT is required', { + expect_error(distance_to_leader(DT = NULL)) +}) + +test_that('arguments required, otherwise error detected', { + expect_error(distance_to_leader(DT, coords = NULL, group = group), + 'coords req') + expect_error(distance_to_leader(DT, coords = coords, group = NULL), + 'group column name required') +}) + +test_that('column names must exist in DT', { + expect_error(distance_to_leader(DT, coords = rep('potato', 2), group = group), + 'potato field') + expect_error(distance_to_leader(DT, coords = coords, group = 'potato'), + 'group column') + copy_DT <- copy(DT) + setnames(copy_DT, 'rank_position_group_direction', 'potato') + expect_error(distance_to_leader(copy_DT, coords = coords, group = group), + 'did you run leader?') +}) + +test_that('coords are correctly provided or error detected', { + expect_error(distance_to_leader(DT, coords = c('X', NULL), group = group), + 'coords requires a vector') + copy_DT <- copy(DT)[, X := as.character(X)] + expect_error(distance_to_leader(copy_DT, coords = coords, group = group), + 'coords must be numeric') + copy_DT <- copy(DT)[, X := as.character(X)] + expect_error(distance_to_leader(copy_DT, coords = coords, + group = group), + 'coords must be numeric') + + copy_DT <- copy(DT)[, rank_position_group_direction := NULL] + expect_error(distance_to_leader(copy_DT, coords = coords, + group = group)) +}) + +test_that('message when distance_leader column overwritten', { + copyDT <- copy(clean_DT)[, distance_leader := 1] + expect_message( + distance_to_leader(copyDT, coords = coords, group = group), + 'distance_leader column will be overwritten' + ) +}) + +test_that('no rows are added to the result DT', { + copyDT <- copy(clean_DT) + + expect_equal(nrow(copyDT), + nrow(distance_to_leader(copyDT, coords = coords, group = group))) +}) + +test_that('one column added to the result DT', { + copyDT <- copy(clean_DT) + + expect_equal(ncol(copyDT) + 1, + ncol(distance_to_leader(copyDT, coords = coords, group = group))) +}) + +test_that('column added to the result DT is a double', { + expect_type( + distance_to_leader(DT, coords = coords, group = group)$distance_leader, + 'double' + ) +}) + +test_that('zzz columns not added to the result', { + zzz_cols <- c('has_leader', 'zzz_N_by_group') + + expect_false( + any(zzz_cols %in% colnames(direction_to_leader(DT, coords = coords))) + ) +}) + +test_that('returns a data.table', { + expect_s3_class(distance_to_leader(DT, coords = coords, group = group), + 'data.table') +}) + + +expect_DT <- data.table( + ID = c('A', 'B'), + X = c(0, 10), + Y = c(0, 0), + group_direction = rep(as_units(0, 'rad'), 2), + group = c(1, 1) +) +centroid_group(expect_DT, coords = coords) +leader_direction_group(expect_DT, coords = coords, + return_rank = TRUE, group = group) +distance_to_leader(expect_DT, coords = c('X', 'Y')) + +test_that('expected results for simple case', { + expect_lte( + expect_DT[, max(distance_leader)], + 10 + ) + + expect_gte( + expect_DT[, min(distance_leader)], + 0 + ) + expect_equal( + expect_DT[distance_leader == min(distance_leader), ID], + 'B' + ) +}) + +