Skip to content

Commit af8cb2e

Browse files
authored
Review for headings+speeds plus split and merge plus misc small changes (#11)
* wip * add initial tests for heading and speed temporal * finish head_speed_temporal review * fixupo * improve arg checking * Add tests for centroid * add tests of speed * fixup * test group heading/speed * fixup * fix typo * initial testing for splits and merges * add shuffle * Finish up identify_splits_and_merge test * analyze split and merge * Refactor heading and speed tests * WIP * clean up tests * lint helpers * lint tests * Deal with build check warnings * add some visualization scripts * fix some checks * fix final warning * add locker to renv file
1 parent e6892d1 commit af8cb2e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+1532
-246
lines changed

.Rbuildignore

+2
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@
66
^docs$
77
^pkgdown$
88
^\.github$
9+
^\.vscode$
10+
^\.lintr$

.lintr

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
linters: linters_with_defaults(
2+
line_length_linter(120),
3+
indentation_linter(hanging_indent_style = 'never'),
4+
object_name_linter(
5+
styles = c("snake_case", "symbols"),
6+
regexes = c('allowed_single_letters' = c('N'))
7+
),
8+
quotes_linter(delimiter = '\'')
9+
)
10+
exclusions: list('tests', 'R') # Currently only running on tests in inst
11+
encoding: "UTF-8"

DESCRIPTION

+4-3
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@ RoxygenNote: 7.3.2
1616
Imports:
1717
checkmate,
1818
dbscan,
19+
fields,
1920
lubridate,
21+
logger,
22+
magrittr,
2023
sf,
21-
fields,
22-
stringr,
23-
magrittr
24+
stringr
2425
Suggests:
2526
tinytest
2627
URL: https://livingingroups.github.io/cocomo/

NAMESPACE

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export(reformat_movebank_to_matrix)
3434
export(utm_to_latlon)
3535
importFrom(dbscan,dbscan)
3636
importFrom(fields,image.plot)
37+
importFrom(logger,log_info)
3738
importFrom(lubridate,date)
3839
importFrom(lubridate,hour)
3940
importFrom(lubridate,parse_date_time)

R/analyze_split_or_merge_event.R

+71-12
Original file line numberDiff line numberDiff line change
@@ -218,19 +218,78 @@ analyze_split_or_merge_event <- function(events, i,
218218
seconds_per_time_step = 1,
219219
breaks = NULL, break_by_day = F,
220220
make_plot = T){
221-
221+
checkmate::assert_matrix(xs, mode = 'numeric')
222+
checkmate::assert_matrix(ys, mode = 'numeric', nrows = nrow(xs), ncol=ncol(xs))
223+
checkmate::assert_int(i, lower=1)
224+
checkmate::assert_data_frame(events, min.rows=i)
225+
checkmate::assert_names(
226+
names(events), must.include = c('tidx', 'group_A_idxs', 'group_B_idxs', 'group_A', 'group_B', 'event_type', 'big_group_idxs')
227+
)
222228
#get info about the event from the events data frame
223-
t_event <- events$tidx[i] #time of the event
224-
group_A <- events$group_A_idxs[i][[1]] #group A individual idxs
225-
group_B <- events$group_B_idxs[i][[1]] #group B individual idxs
229+
t_event <- checkmate::assert_int(
230+
events$tidx[i], #time of the event
231+
lower = 1,
232+
upper = ncol(xs)
233+
)
234+
group_A <- checkmate::assert_integerish(
235+
events$group_A_idxs[i][[1]], #group A individual idxs
236+
lower = 1,
237+
upper = nrow(xs),
238+
min.len = 1,
239+
max.len = nrow(xs) - 1,
240+
unique = TRUE,
241+
sorted = TRUE,
242+
any.missing = FALSE
243+
)
244+
group_B <- checkmate::assert_integerish(
245+
events$group_B_idxs[i][[1]], #group B individual idxs
246+
lower = 1,
247+
upper = nrow(xs),
248+
len = nrow(xs) - length(group_A),
249+
max.len = nrow(xs) - 1,
250+
unique = TRUE,
251+
sorted = TRUE,
252+
any.missing = FALSE
253+
254+
)
255+
checkmate::assert_null(events$group_C)
256+
226257
group_A_names <- events$group_A[i] #group A names
258+
# checkmate::assert_character(
259+
# len = length(group_A),
260+
# unique = TRUE,
261+
# any.missing = FALSE
262+
# )
263+
227264
group_B_names <- events$group_B[i] #group B names
228-
event_type <- events$event_type[i] #event type - fission, fusion, or shuffle
229-
big_group_idxs <- events$big_group_idxs[i][[1]] #all individuals involved in the event
230-
231-
if(!(event_type %in% c('fission','fusion'))){
232-
stop('event must be a fission or fusion')
233-
}
265+
# checkmate::assert_character(
266+
# len = length(group_B),
267+
# unique = TRUE,
268+
# any.missing = FALSE
269+
# )
270+
event_type <- checkmate::assert_subset(
271+
events$event_type[i], #event type - fission, fusion, or shuffle
272+
c('fission', 'fusion')
273+
)
274+
big_group_idxs <- checkmate::assert_set_equal(
275+
events$big_group_idxs[i][[1]], #all individuals involved in the event
276+
sort(c(group_A, group_B))
277+
)
278+
checkmate::assert_posixct(
279+
timestamps,
280+
len = ncol(xs)
281+
)
282+
checkmate::assert_int(
283+
max_time,
284+
lower = 1
285+
)
286+
checkmate::assert_number(thresh_h)
287+
checkmate::assert_number(thresh_l)
288+
checkmate::assert_number(depart_or_arrive_radius)
289+
checkmate::assert_int(time_window, lower = 0, upper = ncol(xs))
290+
checkmate::assert_number(seconds_per_time_step, lower = 0)
291+
checkmate::assert_flag(break_by_day)
292+
checkmate::assert_flag(make_plot)
234293

235294
#calculate a few metrics from event info
236295
ti <- t_event - max_time #initial time to plot
@@ -249,7 +308,7 @@ analyze_split_or_merge_event <- function(events, i,
249308
}
250309
}
251310

252-
#if braeks is null, create a breaks variable specifying one data chunk
311+
#if breaks is null, create a breaks variable specifying one data chunk
253312
if(is.null(breaks)){
254313
breaks <- c(1, length(timestamps) + 1)
255314
}
@@ -334,7 +393,7 @@ analyze_split_or_merge_event <- function(events, i,
334393
if(upper <= thresh_l){
335394
upper <- thresh_h
336395
}
337-
#likewise for lower bound
396+
#likewise for lower bound
338397
if(lower >= thresh_h){
339398
lower <- thresh_l
340399
}

R/generate_movement_and_calls_visualization.R

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
#' @author Ariana Strandburg-Peshkin (primary author)
1212
#' @author NOT YET CODE REVIEWED
1313
#'
14-
#' @param xs UTM eastings matrix (`n_inds` x `n_times` matrix where xs[i,t] gives the easting of individual i at time step t)
15-
#' @param ys UTM northings matrix (`n_inds` x `n_times` matrix where ys[i,t] gives the northing of individual i at time step t)
14+
#' @param xs UTM eastings matrix (`n_inds` x `n_times` matrix where xs\[i,t\] gives the easting of individual i at time step t)
15+
#' @param ys UTM northings matrix (`n_inds` x `n_times` matrix where ys\[i,t\] gives the northing of individual i at time step t)
1616
#' @param timestamps vector of timestamps (POSIXct), must have same dimensions as columns of `xs` and `ys` matrices
1717
#' @param calls data frame where first column (`'ind_idx'`) specifies the index of the individual that gave the call, second column (`'time_idx'`) specifies the time index at which the call was given, and third column (`'call_type'`) specifies the type of call (character string)
1818
#' @param start_time time index at which to start the video

R/get_angle_between_vectors.R

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#'Vector 1 is defined as the vector pointing from the point `(x1_i, y1_i)` to `(x1_f, y1_f)` and
55
#'vector 2 is defined as the vector point from the point `(x2_i, y2_i)` to `(x2_f, y2_f)`.
66
#'The angle is defined as the angle produced if the two vectors are joined at the initial
7-
#'endpoints (rather than head-to-tail). The reuslting angle is always positive.
7+
#'endpoints (rather than head-to-tail). The resulting angle is always positive.
88
#'
99
#' @author Ariana Strandburg-Peshkin
1010
#' @author NOT YET CODE REVIEWED

R/get_group_centroid.R

+5-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
#'time step.
66
#'
77
#' @author Ariana Strandburg-Peshkin (primary author)
8-
#' @author NOT YET CODE REVIEWED
8+
#' @author Reviewed by Brock
99
#'
1010
#' @param xs `N x n_times` matrix giving x coordinates of each individual over time
1111
#' @param ys `N x n_times` matrix giving y coordinates of each individual over time
@@ -14,6 +14,9 @@
1414
#' @export
1515
#'
1616
get_group_centroid <- function(xs, ys, min_inds_tracked = NULL){
17+
checkmate::assert_matrix(xs, 'numeric')
18+
checkmate::assert_matrix(ys, 'numeric')
19+
checkmate::assert_int(min_inds_tracked, lower=0, upper=nrow(xs), null.ok = TRUE)
1720

1821
#check matrix dimensions
1922
if(nrow(xs) != nrow(ys) || ncol(xs) != ncol(ys)){
@@ -26,7 +29,7 @@ get_group_centroid <- function(xs, ys, min_inds_tracked = NULL){
2629

2730
#replace time points with too few individuals with NAs
2831
if(!is.null(min_inds_tracked)){
29-
n_tracked <- colSums(!is.na(xs))
32+
n_tracked <- colSums(!is.na(xs) & !is.na(ys))
3033
not_enough_inds <- which(n_tracked < min_inds_tracked)
3134
x_centr[not_enough_inds] <- NA
3235
y_centr[not_enough_inds] <- NA

R/get_group_heading_and_speed.R

+18-20
Original file line numberDiff line numberDiff line change
@@ -4,45 +4,43 @@
44
#' going either forward (into the future) or backward (into the past) when computing the heading
55
#'
66
#' @author Ariana Strandburg-Peshkin (primary author)
7-
#' @author NOT YET CODE REVIEWED
7+
#' @author Reviewed by Brock
88
#'
99
#' @param xs `N x n_times` matrix giving x coordinates of each individual over time
1010
#' @param ys `N x n_times` matrix giving y coordinates of each individual over time
1111
#' @param heading_type character string specifying heading type - `'spatial'` or `'temporal'`
1212
#' @param spatial_R radius to use for spatial headings (if `heading_type = 'spatial'`)
1313
#' @param t_window temporal window to use for temporal headings (if `heading_type = 'temporal'`)
1414
#' @param forward whether to compute headings into the future (`forward = T`) or the past (`forward = F`)
15-
#' @param min_inds_tracked if specified, sets a minimum number of individuals that must be tracked at any moment in time to compute heading (otherwise the heading will be NA at that time point
15+
#' @param min_inds_tracked if specified, sets a minimum number of individuals that must be tracked to use that time point in computing heading. headings, speeds, and dt that would rely on data with an insufficient number of individuals will be reported as NA.
1616
#' @param seconds_per_time_step number of seconds corresponding to each time step
1717
#'
1818
#' @returns Returns the group heading over time, a vector of length `n_times`
1919
#' @export
2020
#'
2121
get_group_heading_and_speed <- function(xs, ys, heading_type, spatial_R = NULL, t_window = NULL, forward = T, min_inds_tracked = NULL, seconds_per_time_step = 1){
22+
checkmate::assert_matrix(xs, 'numeric')
23+
checkmate::assert_matrix(ys, 'numeric')
24+
checkmate::assert_subset(heading_type, c('spatial', 'temporal'), empty.ok = FALSE)
25+
if(heading_type == 'spatial'){
26+
checkmate::assert_number(spatial_R)
27+
if(!is.null(t_window)) warning('heading_type is set to spatial so t_window argument is ignored')
28+
} else {
29+
if(!is.null(spatial_R)) warning('heading_type is set to temporal so spatial_R argument is ignored')
30+
checkmate::assert_int(t_window, lower = 1, upper = ncol(xs))
31+
}
32+
checkmate::assert_logical(forward)
33+
checkmate::assert_int(min_inds_tracked, lower=0, upper=nrow(xs), null.ok = TRUE)
34+
checkmate::assert_number(seconds_per_time_step, lower = 0)
35+
2236

23-
#TODO: Think about what to do if number of tracked individuals changes - should probably have heading = NA at those times
37+
#TODO: Think about what to do if number of tracked individuals changes - should probably have heading = NA at those times
2438

2539
#check matrix dimensions
2640
if(nrow(xs) != nrow(ys) || ncol(xs) != ncol(ys)){
2741
stop('xs and ys matrices must have same dimensions')
2842
}
2943

30-
#check that the required variables exist for computing headings
31-
if(heading_type %in% c('spatial','temporal')){
32-
if(heading_type == 'spatial'){
33-
if(is.null(spatial_R)){
34-
stop('Must specify spatial_R for spatial headings')
35-
}
36-
}
37-
if(heading_type == 'temporal'){
38-
if(is.null(t_window)){
39-
stop('Must specify t_window for temporal headings')
40-
}
41-
}
42-
} else{
43-
stop('Must specify heading_type as either spatial or temporal')
44-
}
45-
4644
#get centroid trajectory
4745
centr_xy <- cocomo::get_group_centroid(xs = xs, ys = ys, min_inds_tracked = min_inds_tracked)
4846
x_centr <- centr_xy$x_centr
@@ -51,7 +49,7 @@ get_group_heading_and_speed <- function(xs, ys, heading_type, spatial_R = NULL,
5149
#get heading
5250
if(heading_type == 'temporal'){
5351
heads_speeds <- cocomo::get_heading_and_speed_temporal(x_i = x_centr, y_i = y_centr, t_window = t_window, forward = forward, seconds_per_time_step = seconds_per_time_step)
54-
} else{
52+
} else {
5553
heads_speeds <- cocomo::get_heading_and_speed_spatial(x_i = x_centr, y_i = y_centr, R = spatial_R, forward = forward, seconds_per_time_step = seconds_per_time_step )
5654
}
5755

R/get_heading_and_speed_spatial.R

+26-8
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,42 @@
55
#'its location after it mas moved a distance of at least `R`.
66
#'
77
#' @author Ariana Strandburg-Peshkin (primary author)
8-
#' @author NOT YET CODE REVIEWED
8+
#' @author Reviewed by Brock
99
#'
1010
#' @param x_i vector of x coordinates for the trajectory
1111
#' @param y_i vector of y coordinates for the trajectory
1212
#' @param R radius used to compute the headings
13-
#' @param t_idxs: time indexes at which to compute the headings (defaults to entire trajectory)
13+
#' @param t_idxs time indexes at which to compute the headings, speed, dts (defaults to entire trajectory)
1414
#' @param forward whether to go forward in time from current position (if T), or backward (if F) when computing headings
1515
#' @param seconds_per_time_step number of seconds corresponding to each time step
1616
#'
17-
#' @returns Returns a list containing `$heads`: a time series of the heading of
18-
#' the individual (a vector of the same length as x_i and y_i), in radians, computed based on
19-
#' spatial discretization around the time point,`$speeds`: a time series of the speed of the individual at each time point, and
20-
#' `$dts`: time differences between the current point and the first point outside of the radius R (either forward or backward in time)
17+
#' @returns Returns a list containing
18+
#' * `$heads`: a time series of the heading of the individual (a vector of the same length as x_i and y_i), in radians, computed based on
19+
#' spatial discretization around the time point
20+
#' * `$speeds`: a time series of the average speed of the individual from the first point outside radius R to the current point
21+
#' * `$dts`: time differences between the current point and the first point outside of the radius R (either forward or backward in time)
2122
#'
2223
#' vector of spatially discretized headings, computed at all times or at times t_idxs if specified (other times are then filled in with NAs)
2324
#' @export
2425
get_heading_and_speed_spatial <- function(x_i, y_i, R, t_idxs=1:length(x_i), forward=T, seconds_per_time_step = 1){
2526

27+
checkmate::assert_numeric(x_i)
28+
checkmate::assert_numeric(y_i)
29+
checkmate::assert_number(R, lower = 0)
30+
checkmate::assert_integerish(
31+
t_idxs,
32+
lower = 1,
33+
upper = length(x_i),
34+
any.missing = FALSE,
35+
min.len = 1,
36+
max.len = length(x_i),
37+
unique = TRUE,
38+
typed.missing = FALSE
39+
)
40+
checkmate::assert_logical(forward, len = 1)
41+
checkmate::assert_number(seconds_per_time_step, lower = 0)
42+
43+
2644
#check that x_i and y_i are the same length
2745
if(length(x_i) != length(y_i)){
2846
stop('x_i and y_i must be vectors of the same length')
@@ -56,9 +74,9 @@ get_heading_and_speed_spatial <- function(x_i, y_i, R, t_idxs=1:length(x_i), for
5674
#move forward (or backward) until radius reached
5775
found <- 0
5876
na_found <- 0
59-
time_vec <- t:n_times
77+
time_vec <- min((t+1), n_times):n_times
6078
if(!forward){
61-
time_vec <- seq(t,1,-1)
79+
time_vec <- max((t-1), 1):1
6280
}
6381
for(i in time_vec){
6482

R/get_heading_and_speed_temporal.R

+3-3
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
#'
1111
#'
1212
#' @author Ariana Strandburg-Peshkin
13-
#' @author NOT YET CODE REVIEWED
13+
#' @author Reviewed by Brock
1414
#'
1515
#' @param x_i x coordinates of the individual (a vector whose length is the number of timesteps) or of a group centroid
1616
#' @param y_i y coordinates of the individual (a vector whose length is the number of timesteps) or of the group centroid
@@ -27,7 +27,7 @@ get_heading_and_speed_temporal <- function(x_i, y_i, t_window = 1, forward = T,
2727
checkmate::assert_numeric(y_i)
2828
checkmate::assert_int(t_window, lower = 1, upper = length(x_i))
2929
checkmate::assert_logical(forward, len = 1)
30-
checkmate::assert_numeric(seconds_per_time_step, lower = 0, len = 1)
30+
checkmate::assert_number(seconds_per_time_step, lower = 0)
3131

3232

3333

@@ -69,8 +69,8 @@ get_heading_and_speed_temporal <- function(x_i, y_i, t_window = 1, forward = T,
6969
heads <- atan2(head_y, head_x)
7070

7171
#output
72-
out$speeds <- ds / (t_window * seconds_per_time_step)
7372
out$heads <- heads
73+
out$speeds <- ds / (t_window * seconds_per_time_step)
7474
out$dts <- rep(t_window * seconds_per_time_step, len)
7575

7676
return(out)

R/get_pulls_and_anchors.R

+10-10
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@
55
#' @author Ariana Strandburg-Peshkin (primary author)
66
#' @author NOT YET CODE REVIEWED
77
#'
8-
#' @param xa: x coordinates for individual a (vector of length `n_times`)- must be continuous data (no NAs)
9-
#' @param xb: x coordinates for individual b (vector of length `n_times`)- must be continuous data (no NAs)
10-
#' @param ya: x coordinates for individual a (vector of length `n_times`)- must be continuous data (no NAs)
11-
#' @param yb: x coordinates for individual b (vector of length `n_times`)- must be continuous data (no NAs)
12-
#' @param a: index of the first individual
13-
#' @param b: index of the second individual
14-
#' @param noise_thresh: noise threshold (defaults to 5 m)
15-
#' @param plot_results: whether to plot results or not
16-
#' @param include_initial_fusion: if T, the function will also output an initial fusion event. In most use cases, this should be set to false. See below for details.
17-
#' @param include_final_fission: if T, the funciton will also output a final fission event. In most use cases, this should be set to false. See below for details.
8+
#' @param xa x coordinates for individual a (vector of length `n_times`)- must be continuous data (no NAs)
9+
#' @param xb x coordinates for individual b (vector of length `n_times`)- must be continuous data (no NAs)
10+
#' @param ya x coordinates for individual a (vector of length `n_times`)- must be continuous data (no NAs)
11+
#' @param yb x coordinates for individual b (vector of length `n_times`)- must be continuous data (no NAs)
12+
#' @param a index of the first individual
13+
#' @param b index of the second individual
14+
#' @param noise_thresh noise threshold (defaults to 5 m)
15+
#' @param plot_results whether to plot results or not
16+
#' @param include_initial_fusion if T, the function will also output an initial fusion event. In most use cases, this should be set to false. See below for details.
17+
#' @param include_final_fission if T, the funciton will also output a final fission event. In most use cases, this should be set to false. See below for details.
1818
#'
1919
#' @section Details on fission and fusion events:
2020
#'

0 commit comments

Comments
 (0)