Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/directional correlation delay #70

Merged
merged 107 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
107 commits
Select commit Hold shift + click to select a range
483b59e
fst edge delay
robitalec Jul 19, 2024
1e30cc8
Merge branch 'main' into feat/directional-correlation-delay
robitalec Nov 1, 2024
0b89f6d
internal delta_rad function
robitalec Nov 1, 2024
455568e
title
robitalec Nov 19, 2024
a458550
description
robitalec Nov 19, 2024
79d6792
params
robitalec Nov 19, 2024
6f864c6
return
robitalec Nov 19, 2024
7dfb16a
example
robitalec Nov 19, 2024
331960d
args
robitalec Nov 19, 2024
0288635
rm timegroup arg
robitalec Nov 19, 2024
1e431c4
fix id colname
robitalec Nov 19, 2024
567aba6
set window default null
robitalec Nov 19, 2024
1b25f48
check dt
robitalec Nov 19, 2024
2918e1c
check edges
robitalec Nov 19, 2024
891b1c0
check id colname
robitalec Nov 19, 2024
03908e3
check cols
robitalec Nov 19, 2024
a721039
check window
robitalec Nov 19, 2024
b74e4b8
check dyad, fusion ids
robitalec Nov 19, 2024
a94bada
fix check numerator brackets
robitalec Nov 19, 2024
294d5ce
fix units
robitalec Nov 19, 2024
595ce9d
check if window in colnames, zz temporarily if so
robitalec Nov 19, 2024
f85b1e8
use three zees
robitalec Nov 19, 2024
2f434ca
tidy
robitalec Nov 19, 2024
f55753c
fix direction not bearing
robitalec Nov 19, 2024
42fbf67
tidy
robitalec Nov 19, 2024
4446527
fix reset zzz id
robitalec Nov 19, 2024
981c2ac
todo check max tg
robitalec Nov 19, 2024
ba6a834
Merge branch 'main' into feat/directional-correlation-delay
robitalec Nov 19, 2024
d3b1cbb
fix timegroup char since not arg
robitalec Nov 19, 2024
d54bf05
rm setnames, use env
robitalec Nov 19, 2024
ada1d55
fst test edge_delay
robitalec Nov 19, 2024
5538f30
test setup
robitalec Nov 19, 2024
ac2091a
reorg args
robitalec Nov 19, 2024
21808d2
test required arg
robitalec Nov 19, 2024
6a12bb5
test window numeric
robitalec Nov 19, 2024
8ad56a5
test colnames exist
robitalec Nov 19, 2024
ccadff4
test returned object
robitalec Nov 19, 2024
7752d04
fix missing coords arg
robitalec Nov 19, 2024
90bc8ed
fix use copy edges/dt
robitalec Nov 20, 2024
116dab6
fix len to match expected output cols
robitalec Nov 20, 2024
86c2b96
fix integer returned
robitalec Nov 20, 2024
2f67e07
fix potato field
robitalec Nov 20, 2024
39fc136
fix make consistent error msg
robitalec Nov 20, 2024
68d9221
todo expected tests
robitalec Nov 20, 2024
80d6150
Merge branch 'main' into feat/directional-correlation-delay
robitalec Nov 26, 2024
52ed518
fst expected results test
robitalec Nov 26, 2024
95646f8
note requires version of units with PR fixing modulus operator
robitalec Nov 26, 2024
d39acb4
rename function
robitalec Nov 26, 2024
ed784cb
rename args
robitalec Nov 26, 2024
f88f613
fst test diff_rad
robitalec Nov 26, 2024
adf903d
setup
robitalec Nov 26, 2024
c23f786
test diff rad
robitalec Nov 26, 2024
3f30aa1
also test not signed
robitalec Nov 26, 2024
51fb9c1
add example
robitalec Nov 26, 2024
69c5e45
extra brackets
robitalec Nov 26, 2024
df9558f
edit description
robitalec Dec 6, 2024
824e34d
add references
robitalec Dec 6, 2024
2390e5f
add family
robitalec Dec 6, 2024
83ca625
clarify also accepts edge_nn
robitalec Dec 6, 2024
3d31672
use renamed internal diff_rad function
robitalec Dec 6, 2024
887797d
fix test cols added to result
robitalec Dec 6, 2024
26382b3
fix use first(.SD) to preserve any extra cols in edges
robitalec Dec 6, 2024
d68e47b
use "forward" obj name to represent ID1 -> ID2
robitalec Dec 6, 2024
108a9b8
use "reverse" obj name to represent ID2 -> ID1
robitalec Dec 6, 2024
b6a5cbc
fix max tg
robitalec Dec 6, 2024
2d9b1d4
fix use env arg
robitalec Dec 6, 2024
b10428c
use fusionID directly
robitalec Dec 6, 2024
7ec2d86
fix long line
robitalec Dec 6, 2024
51487f0
test direction colname can differ from default
robitalec Dec 6, 2024
e0bedb3
fix add direction to env
robitalec Dec 6, 2024
a0f90bf
test window column in edge or DT has no impact
robitalec Dec 6, 2024
2be21f5
fix potential window colname mixup with env
robitalec Dec 6, 2024
7ae21d3
tidy window
robitalec Dec 6, 2024
2e73b4e
fix setcolorder
robitalec Dec 6, 2024
db789eb
fix arg names
robitalec Dec 6, 2024
6be2057
return
robitalec Dec 6, 2024
00561b7
tidy window
robitalec Dec 6, 2024
1c78947
fix orders
robitalec Dec 6, 2024
f49117f
fix drop units to improve efficiency
robitalec Dec 6, 2024
bea4b33
bump required data.table version
robitalec Dec 6, 2024
b7c9bf1
test timegroup is integer
robitalec Dec 6, 2024
28c8f56
check timegroup is integer
robitalec Dec 6, 2024
cba1cf9
fix typo
robitalec Dec 6, 2024
9bf16a9
add return_units arg
robitalec Dec 6, 2024
113f6de
use return_units arg
robitalec Dec 6, 2024
a3a0a96
fix typo
robitalec Dec 6, 2024
2302498
todo test expected dataset
robitalec Dec 6, 2024
0a8e019
test expected dataset
robitalec Dec 6, 2024
202e752
fix nse notes
robitalec Dec 6, 2024
c03dc34
describe positive edge is ID1 leading ID2
robitalec Dec 9, 2024
31b18b9
fix ref
robitalec Dec 9, 2024
a58de40
print positive edges
robitalec Dec 9, 2024
f4e34e5
rm
robitalec Dec 9, 2024
7576827
fix positive = lead
robitalec Dec 9, 2024
e5e82a3
fix missing data.table::
robitalec Dec 9, 2024
3d41ba3
fix example of internal f
robitalec Dec 9, 2024
884a6bc
extend expected with more xy
robitalec Dec 9, 2024
3bfd7a5
rm
robitalec Dec 9, 2024
3b8b9f9
increase window
robitalec Dec 9, 2024
84d8386
test for positive edges
robitalec Dec 9, 2024
69dbc6e
needs modulus fix in units
robitalec Dec 9, 2024
a5735da
try short commit sha
robitalec Dec 9, 2024
8af8fdb
fix note dyadID
robitalec Dec 9, 2024
460949d
more arg docs
robitalec Dec 9, 2024
f2d80f4
fix missing \
robitalec Dec 9, 2024
1e4c5c0
add test for types
robitalec Dec 9, 2024
3e96e6f
man
robitalec Dec 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Depends:
R (>= 3.4)
Imports:
adehabitatHR (>= 0.4.21),
data.table (>= 1.10.5),
data.table (>= 1.15.0),
igraph,
sf,
lwgeom,
Expand All @@ -43,3 +43,4 @@ Roxygen: list(markdown = TRUE)
RoxygenNote: 7.3.2
SystemRequirements: GDAL (>= 2.0.1), GEOS (>= 3.4.0), PROJ (>= 4.8.0),
sqlite3
Remotes: r-quantities/units@889cf39
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export(direction_to_leader)
export(distance_to_centroid)
export(distance_to_leader)
export(dyad_id)
export(edge_delay)
export(edge_dist)
export(edge_nn)
export(fusion_id)
Expand Down
228 changes: 228 additions & 0 deletions R/edge_delay.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
#' Directional correlation delay based edge lists
#'
#' \code{edge_delay} returns edge lists defined by the directional correlation
#' delay between individuals. The function expects a distance based edge list
#' generated by \code{edge_dist} or \code{edge_nn}, a \code{data.table} with
#' relocation data, individual identifiers and a window argument. The window
#' argument is used to specify the temporal window within which to measure the
#' directional correlation delay. Relocation data should be in two columns
#' representing the X and Y coordinates.
#'
#' The \code{edges} and \code{DT} must be \code{data.table}s. If your data is a
#' \code{data.frame}, you can convert it by reference using
#' \code{\link[data.table:setDT]{data.table::setDT}}.
#'
#' The \code{edges} argument expects a distance based edge list generated with
#' \code{edge_nn} or \code{edge_dist}. The \code{DT} argument expects relocation
#' data with a timegroup column generated with \code{group_times}.
#'
#' The rows in \code{edges} and \code{DT} are internally matched in
#' \code{edge_delay} using the columns \code{timegroup} (from
#' \code{group_times}) and \code{ID1} and \code{ID2} (in \code{edges}, from
#' \code{dyad_id}) with \code{id} (in \code{DT}). This function expects a
#' \code{fusionID} present, generated with the \code{fusion_id} function, and a
#' \code{dyadID} present, generated with the \code{dyad_id} function. The
#' \code{id}, and \code{direction} arguments expect the names of a column in
#' \code{DT} which correspond to the id, and direction columns.
#'
#' @inheritParams centroid_fusion
#' @inheritParams direction_group
#' @param window temporal window in unit of timegroup column generated with
#' \code{group_times}, eg. \code{window = 4} corresponds to the 4 timegroups
#' before and after the focal observation
#'
#' @return \code{edge_delay} returns the input \code{edges} appended with
#' a 'dir_corr_delay' column indicating the temporal delay (in units of
#' timegroups) at which ID1's direction of movement is most similar to
#' ID2's direction of movement, within the temporal window defined. For
#' example, if focal individual 'A' moves in a 45 degree direction at time 2
#' and individual 'B' moves in a most similar direction within the window
#' at time 5, the directional correlation delay between A and B is 3. Positive
#' values of directional correlation delay indicate a directed leadership
#' edge from ID1 to ID2.
#'
#' @export
#'
#' @references
#'
#' The directional correlation delay is defined in Nagy et al. 2010
#' (<https://doi.org/10.1038/nature08891>).
#'
#' See examples of measuring the directional correlation delay:
#' * <https://doi.org/10.1016/j.anbehav.2013.07.005>
#' * <https://doi.org/10.1073/pnas.1305552110>
#' * <https://doi.org/10.1111/jfb.15315>
#' * <https://doi.org/10.1371/journal.pcbi.1003446>
#'
#' @family Edge-list generation
#' @family Direction functions
#'
#' @examples
#' # Load data.table
#' library(data.table)
#' \dontshow{data.table::setDTthreads(1)}
#'
#' # Read example data
#' DT <- fread(system.file("extdata", "DT.csv", package = "spatsoc"))
#'
#' # Select only individuals A, B, C for this example
#' DT <- DT[ID %in% c('A', 'B', 'C')]
#'
#' # Cast the character column to POSIXct
#' DT[, datetime := as.POSIXct(datetime, tz = 'UTC')]
#'
#' # Temporal grouping
#' group_times(DT, datetime = 'datetime', threshold = '20 minutes')
#'
#' # Calculate direction
#' direction_step(
#' DT = DT,
#' id = 'ID',
#' coords = c('X', 'Y'),
#' projection = 32736
#' )
#'
#' # Distance based edge list generation
#' edges <- edge_dist(
#' DT,
#' threshold = 100,
#' id = 'ID',
#' coords = c('X', 'Y'),
#' timegroup = 'timegroup',
#' returnDist = TRUE,
#' fillNA = FALSE
#' )
#'
#' # Generate dyad id
#' dyad_id(edges, id1 = 'ID1', id2 = 'ID2')
#'
#' # Generate fusion id
#' fusion_id(edges, threshold = 100)
#'
#' # Directional correlation delay
#' delay <- edge_delay(
#' edges = edges,
#' DT = DT,
#' window = 3,
#' id = 'ID'
#' )
#'
#' delay[, mean(dir_corr_delay, na.rm = TRUE), by = .(ID1, ID2)][V1 > 0]
edge_delay <- function(
edges,
DT,
window = NULL,
id = NULL,
direction = 'direction') {
# due to NSE notes in R CMD check
. <- timegroup <- fusionID <- min_timegroup <- max_timegroup <-
delay_timegroup <- ID1 <- ID2 <- dir_corr_delay <- NULL

if (is.null(DT)) {
stop('input DT required')
}

if (is.null(edges)) {
stop('input edges required')
}

if (is.null(id)) {
stop('id column name required')
}

check_cols_edges <- c('ID1', 'ID2', 'timegroup')
if (any(!(check_cols_edges %in% colnames(edges)))) {
stop(paste0(
as.character(paste(setdiff(
check_cols_edges,
colnames(edges)
), collapse = ', ')),
' field(s) provided are not present in input DT'
))
}

check_cols_DT <- c(id, 'timegroup', direction)
if (any(!(check_cols_DT %in% colnames(DT)
))) {
stop(paste0(
as.character(paste(setdiff(
check_cols_DT,
colnames(DT)
), collapse = ', ')),
' field(s) provided are not present in input DT'
))
}

if (is.null(window)) {
stop('window is required')
}

if (!is.numeric(window)) {
stop('window should be a numeric, in the units of timegroup')
}

if (!is.integer(DT$timegroup) || !is.integer(edges$timegroup)) {
stop('timegroup should be an integer, did you use group_times?')
}

if (!'fusionID' %in% colnames(edges)) {
stop('fusionID field not present in edges, did you run fusion_id?')
}

if (!'dyadID' %in% colnames(edges)) {
stop('dyadID field not present in edges, did you run dyad_id?')
}

# "Forward": all edges ID1 -> ID2
forward <- edges[!is.na(fusionID),
data.table::first(.SD),
by = .(fusionID, timegroup)]

forward[, min_timegroup :=
data.table::fifelse(timegroup - window < min(timegroup),
min(timegroup),
timegroup - window),
by = fusionID,
env = list(window = window)]

forward[, max_timegroup :=
data.table::fifelse(timegroup + window > max(timegroup),
max(timegroup),
timegroup + window),
by = fusionID,
env = list(window = window)]

forward[, delay_timegroup := {
focal_direction <- DT[timegroup == .BY$timegroup &
id == ID1, direction]
DT[between(timegroup, min_timegroup, max_timegroup) & id == ID2,
timegroup[which.min(diff_rad(focal_direction, direction))]]
},
by = c('timegroup', 'dyadID'),
env = list(id = id, direction = direction)]

forward[, dir_corr_delay := delay_timegroup - timegroup]

data.table::set(forward,
j = c('min_timegroup', 'max_timegroup','delay_timegroup'),
value = NULL)

# "Reverse": replicate forward but reverse direction ID1 <- ID2
reverse <- data.table::copy(forward)
setnames(reverse, c('ID1', 'ID2'), c('ID2', 'ID1'))
reverse[, dir_corr_delay := - dir_corr_delay]

out <- data.table::rbindlist(list(
forward,
reverse
), use.names = TRUE)

data.table::setorder(out, timegroup)
data.table::setcolorder(
out,
c('timegroup', 'ID1', 'ID2', 'dyadID', 'fusionID')
)

return(out)
}

63 changes: 63 additions & 0 deletions R/internal.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#' Difference of two angles measured in radians
#'
#' Internal function
#'
#' @param x angle in radians
#' @param y angle in radians
#' @param signed boolean if signed difference should be returned, default FALSE
#' @param return_units return difference with units = 'rad'
#'
#' @return Difference between x and y in radians. If signed is TRUE, the signed difference is returned. If signed is FALSE, the absolute difference is returned. Note: The difference is the smallest difference, eg.
#' @references adapted from https://stackoverflow.com/a/7869457
#'
#' @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')]
#'
#' # Set order using data.table::setorder
#' setorder(DT, datetime)
#'
#' # Calculate direction
#' direction_step(
#' DT = DT,
#' id = 'ID',
#' coords = c('X', 'Y'),
#' projection = 32736
#' )
#'
#' # Differences
#' spatsoc:::diff_rad(DT[1, direction], DT[2, direction])
diff_rad <- function(x, y, signed = FALSE, return_units = FALSE) {
if (!inherits(x, 'units') || units(x)$numerator != 'rad') {
stop('units(x) is not radians')
}
if (!inherits(y, 'units') || units(y)$numerator != 'rad') {
stop('units(y) is not radians')
}

d <- units::drop_units(y) - units::drop_units(x)
d <- ((d + pi) %% (2 * pi)) - pi

if (signed) {
out <- d
} else {
out <- abs(d)
}

if (return_units) {
return(units::as_units(out, 'rad'))
} else {
return(out)
}
}


# Requires version with this PR merged
# remotes::install_github('https://github.com/r-quantities/units/pull/365')
4 changes: 2 additions & 2 deletions codemeta.json
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@
"@type": "SoftwareApplication",
"identifier": "data.table",
"name": "data.table",
"version": ">= 1.10.5",
"version": ">= 1.15.0",
"provider": {
"@id": "https://cran.r-project.org",
"@type": "Organization",
Expand Down Expand Up @@ -214,7 +214,7 @@
},
"SystemRequirements": "GDAL (>= 2.0.1), GEOS (>= 3.4.0), PROJ (>= 4.8.0),\n sqlite3"
},
"fileSize": "2023.039KB",
"fileSize": "2044.39KB",
"citation": [
{
"@type": "ScholarlyArticle",
Expand Down
51 changes: 51 additions & 0 deletions man/diff_rad.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion man/direction_group.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading