Detects significant change points in animal movement trajectory data using a permutation-based approach. This function identifies locations where the movement pattern significantly changes, which can represent behavioral transitions or responses to environmental stimuli.

change_point_test(
  data,
  alpha = 0.05,
  q = 4,
  n = 10000,
  min_move_dist = 0,
  clu = NULL,
  seed = NULL,
  ...
)

# S3 method for class 'trackframe'
change_point_test(
  data,
  alpha = 0.05,
  q = 4,
  n = 1000,
  min_move_dist = 0,
  clu = NULL,
  seed = NULL,
  ...
)

# S3 method for class 'data.frame'
change_point_test(
  data,
  alpha = 0.05,
  q = 4,
  n = 1000,
  min_move_dist = 0,
  clu = NULL,
  seed = NULL,
  tf_args = list(crs = NA),
  ...
)

# S3 method for class 'move2'
change_point_test(
  data,
  alpha = 0.05,
  q = 4,
  n = 1000,
  min_move_dist = 0,
  clu = NULL,
  seed = NULL,
  ...
)

# S3 method for class 'sftrack'
change_point_test(
  data,
  alpha = 0.05,
  q = 4,
  n = 1000,
  min_move_dist = 0,
  clu = NULL,
  seed = NULL,
  ...
)

Arguments

data

a trackframe, or an object coercible to trackframe

alpha

a numeric value specifying the significance level for detecting change points.

q

an integer specifying the minimum segment length between potential change points.

n

an integer specifying the number of random permutations for thepermutation test. Higher values provide more accurate p-values but increase computation time.

min_move_dist

a numeric value specifying the minimum distance between two positions to be distinguishable. Points with movements smaller than this threshold will be considered stationary.(tol parameter in original code)

clu

optional parameter determining whether parallelization with the parallel package is used. Either of class "NULL", "numeric", or "cluster":

  • If NULL (default) no parallel processing is used.

  • If of class "numeric", it gives the number of cores, passed as integer to parallel::makePSOCKcluster.

  • If of class "cluster", it is assumed to be a cluster object from parallel package. Allowed is any object which inherits from "cluster" and can be passed to parallel::parLapply.

seed

seed to be passed to random number generator

...

additional arguments passed to methods.

tf_args

args passed to as.trackframe in the case "data" is not already a trackframe

Value

An augmented data frame containing the original data with additional columns:

cp_id

Sequential numbering of detected change points

Details

This function implements a sequential change point detection algorithm that uses a permutation test to identify significant changes in movement patterns. It compares the sum of distances between consecutive points against randomly permuted sequences to determine if a change point exists.

The first point in the trajectory is never considered a change point, as it represents the starting location.

Examples

library("cpt")
# First detect change points
set.seed(2025L)
cpt <- change_point_test(cpttestdata, alpha = 0.05, q = 3, n = 500)

# Second show the change points in a summarized format
summary(cpt)
#>                         first                last     east    north      id
#> track_1.1 2025-01-01 00:38:00 2025-01-01 00:38:00 588443.2 703275.0 track_1
#> track_1.2 2025-01-01 00:54:00 2025-01-01 00:54:00 587898.1 703728.7 track_1

# with trackframe
library("trackframe")
#> 
#> Attaching package: ‘trackframe’
#> The following object is masked from ‘package:zoo’:
#> 
#>     time<-
df <- data.frame(x = cpttestdata[, 1],
                 y = cpttestdata[, 2],
                 t = as.POSIXct(seq_along(cpttestdata[, 3])),
                 id = "track_1")
tf <- as.trackframe(df, time_col = 't', easting_col = 'x', northing_col = 'y', crs = NA)
set.seed(2025L)
cpt_tf <- change_point_test(tf, alpha = 0.05, q = 3, n = 500, min_move_dist = 0)
summary(cpt_tf)
#>                         first                last     east    north      id
#> track_1.1 1970-01-01 00:00:39 1970-01-01 00:00:39 588443.2 703275.0 track_1
#> track_1.2 1970-01-01 00:00:55 1970-01-01 00:00:55 587898.1 703728.7 track_1

# Get probability values instead of binary indicators
pvalues <- change_point_test_pvalue(
  cpttestdata[, c("x", "y", "t", "track_id")], q_max = 3, n = 500)
pvalues
#> $track_1
#>          q=1   q=2   q=3
#>   [1,] 1.000 0.676 0.246
#>   [2,]    NA    NA    NA
#>   [3,]    NA    NA    NA
#>   [4,]    NA    NA    NA
#>   [5,]    NA    NA    NA
#>   [6,]    NA    NA    NA
#>   [7,]    NA    NA    NA
#>   [8,] 0.356 0.330 0.372
#>   [9,] 1.000 0.876 0.900
#>  [10,] 0.410 0.402 0.562
#>  [11,] 1.000 0.840 0.624
#>  [12,]    NA    NA    NA
#>  [13,]    NA    NA    NA
#>  [14,]    NA    NA    NA
#>  [15,]    NA    NA    NA
#>  [16,]    NA    NA    NA
#>  [17,] 0.846 0.482 0.282
#>  [18,] 0.244 0.148 0.140
#>  [19,] 0.532 0.348 0.244
#>  [20,] 0.474 0.342 0.284
#>  [21,] 0.528 0.356 0.346
#>  [22,]    NA    NA    NA
#>  [23,] 0.768 0.520 0.796
#>  [24,] 0.480 0.838 0.758
#>  [25,] 0.530 0.874 0.836
#>  [26,] 0.438 0.636 0.786
#>  [27,] 1.000 0.992 0.576
#>  [28,]    NA    NA    NA
#>  [29,]    NA    NA    NA
#>  [30,]    NA    NA    NA
#>  [31,]    NA    NA    NA
#>  [32,]    NA    NA    NA
#>  [33,]    NA    NA    NA
#>  [34,]    NA    NA    NA
#>  [35,]    NA    NA    NA
#>  [36,]    NA    NA    NA
#>  [37,]    NA    NA    NA
#>  [38,]    NA    NA    NA
#>  [39,]    NA    NA    NA
#>  [40,]    NA    NA    NA
#>  [41,]    NA    NA    NA
#>  [42,]    NA    NA    NA
#>  [43,]    NA    NA    NA
#>  [44,]    NA    NA    NA
#>  [45,]    NA    NA    NA
#>  [46,]    NA    NA    NA
#>  [47,]    NA    NA    NA
#>  [48,]    NA    NA    NA
#>  [49,]    NA    NA    NA
#>  [50,]    NA    NA    NA
#>  [51,]    NA    NA    NA
#>  [52,]    NA    NA    NA
#>  [53,]    NA    NA    NA
#>  [54,]    NA    NA    NA
#>  [55,]    NA    NA    NA
#>  [56,]    NA    NA    NA
#>  [57,]    NA    NA    NA
#>  [58,]    NA    NA    NA
#>  [59,]    NA    NA    NA
#>  [60,]    NA    NA    NA
#>  [61,] 1.000 0.624 0.750
#>  [62,] 0.418 0.676 0.534
#>  [63,] 0.454 0.542 0.910
#>  [64,] 0.868 0.834 0.660
#>  [65,] 0.898 0.696 0.532
#>  [66,] 0.750 0.572 0.690
#>  [67,] 0.486 0.662 0.400
#>  [68,] 0.346 0.636 0.772
#>  [69,] 0.548 0.598 0.944
#>  [70,]    NA    NA    NA
#>  [71,]    NA    NA    NA
#>  [72,]    NA    NA    NA
#>  [73,]    NA    NA    NA
#>  [74,]    NA    NA    NA
#>  [75,] 0.904 0.316 0.382
#>  [76,] 0.466 0.298 0.244
#>  [77,] 0.770 0.386 0.230
#>  [78,] 0.372 0.208 0.156
#>  [79,] 0.450 0.298 0.134
#>  [80,] 0.694 0.324 0.544
#>  [81,] 0.392 0.690 0.854
#>  [82,] 1.000 0.940 0.894
#>  [83,]    NA    NA    NA
#>  [84,] 0.878 0.914 0.900
#>  [85,] 0.436 0.328 0.022
#>  [86,] 0.154 0.008 0.008
#>  [87,]    NA    NA    NA
#>  [88,]    NA    NA    NA
#>  [89,]    NA    NA    NA
#>  [90,]    NA    NA    NA
#>  [91,]    NA    NA    NA
#>  [92,]    NA    NA    NA
#>  [93,]    NA    NA    NA
#>  [94,]    NA    NA    NA
#>  [95,]    NA    NA    NA
#>  [96,] 0.064 0.012 0.006
#>  [97,] 0.412 0.232 0.210
#>  [98,] 0.670 0.362 0.446
#>  [99,] 0.620 0.660 0.158
#> [100,] 1.000 0.206 0.020
#> [101,] 0.072 0.008 0.004
#> [102,] 0.108 0.024 0.116
#> [103,] 0.494 0.532 0.330
#> [104,] 0.420 0.204 0.002
#> [105,] 0.460 0.006 0.002
#> [106,]    NA    NA    NA
#> [107,]    NA    NA    NA
#> [108,]    NA    NA    NA
#> [109,]    NA    NA    NA
#> [110,]    NA    NA    NA
#> [111,] 0.020 0.004 0.002
#> [112,] 0.048 0.002 0.006
#> [113,] 0.050 0.058 0.008
#> [114,] 0.564 0.022 0.016
#> [115,] 0.052 0.024 0.022
#> [116,] 0.172 0.350 0.256
#> [117,] 0.478 0.362 0.592
#> [118,] 0.506 0.656 0.630
#> [119,]    NA    NA    NA
#> [120,] 0.834 0.866 0.836
#> [121,] 0.464 0.440 0.700
#> [122,] 0.838 0.740 0.734
#> [123,]    NA    NA    NA
#> [124,]    NA    NA    NA
#> [125,]    NA    NA    NA
#> [126,]    NA    NA    NA
#> [127,]    NA    NA    NA
#> [128,]    NA    NA    NA
#> [129,]    NA    NA    NA
#> [130,]    NA    NA    NA
#> [131,]    NA    NA    NA
#> [132,]    NA    NA    NA
#> [133,]    NA    NA    NA
#> [134,]    NA    NA    NA
#> [135,]    NA    NA    NA
#> [136,]    NA    NA    NA
#> [137,]    NA    NA    NA
#> [138,]    NA    NA    NA
#> [139,] 0.602 0.548 0.792
#> [140,] 0.454 0.792 0.502
#> [141,] 0.888 0.858    NA
#> [142,]    NA    NA    NA
#> [143,]    NA    NA    NA
#> [144,]    NA    NA    NA
#> [145,]    NA    NA    NA
#> [146,]    NA    NA    NA
#> [147,]    NA    NA    NA
#> [148,] 0.442    NA    NA
#> [149,]    NA    NA    NA
#> [150,]    NA    NA    NA
#> attr(,"class")
#> [1] "change_point_test_pvalue" "matrix"                  
#> [3] "array"                   
#> 
#> attr(,"class")
#> [1] "change_point_test_pvalue_collection" "list"                               
set.seed(2025L)
cpt_tf <- change_point_test(cpttestdata, alpha = 0.05, q = 3, n = 500, min_move_dist = 0)
summary(cpt_tf)
#>                         first                last     east    north      id
#> track_1.1 2025-01-01 00:38:00 2025-01-01 00:38:00 588443.2 703275.0 track_1
#> track_1.2 2025-01-01 00:54:00 2025-01-01 00:54:00 587898.1 703728.7 track_1

df <- cpttestdata
class(df)
#> [1] "data.frame"
set.seed(2025L)
cpt_df <- change_point_test(df, alpha = 0.05, q = 3, n = 500, min_move_dist = 0)
summary(cpt_df)
#>                         first                last     east    north      id
#> track_1.1 2025-01-01 00:38:00 2025-01-01 00:38:00 588443.2 703275.0 track_1
#> track_1.2 2025-01-01 00:54:00 2025-01-01 00:54:00 587898.1 703728.7 track_1

library(move2)
data("path_move2", package = "trackframe")
class(path_move2)
#> [1] "move2"      "sf"         "data.frame"
set.seed(2025L)
cpt_move2 <- change_point_test(path_move2, alpha = 0.05, q = 3, n = 500, min_move_dist = 0)
summary(cpt_move2)
#>                  first                last          east       north
#> 1  2025-10-15 00:22:34 2025-10-15 00:22:34 -0.0072061100 0.003763871
#> 2  2025-10-15 13:43:34 2025-10-15 13:43:34 -0.0027773379 0.011703586
#> 3  2025-10-15 18:00:34 2025-10-15 18:07:34  0.0002694329 0.009003683
#> 4  2025-10-15 18:49:34 2025-10-15 18:49:34  0.0013820211 0.017076390
#> 5  2025-10-15 20:58:34 2025-10-15 20:58:34 -0.0049726251 0.016002070
#> 6  2025-10-15 23:43:34 2025-10-15 23:43:34  0.0005152087 0.015387244
#> 7  2025-10-16 02:39:34 2025-10-16 02:39:34 -0.0059711263 0.017819524
#> 8  2025-10-16 04:09:34 2025-10-16 04:09:34 -0.0038805680 0.014830813
#> 9  2025-10-16 07:16:34 2025-10-16 07:16:34 -0.0048342032 0.021118660
#> 10 2025-10-16 15:45:34 2025-10-16 15:45:34 -0.0007020970 0.011519583
#> 11 2025-10-16 16:17:34 2025-10-16 16:17:34 -0.0040944078 0.011046036
#> 12 2025-10-16 18:39:34 2025-10-16 18:39:34  0.0005950734 0.007295949
#> 13 2025-10-16 21:48:34 2025-10-16 21:48:34 -0.0050339505 0.004651530
#> 14 2025-10-16 22:02:34 2025-10-16 22:29:34 -0.0031042727 0.008906007
#> 15 2025-10-17 04:37:34 2025-10-17 04:37:34 -0.0053506489 0.005985680

library(sftrack)
data("path_sftrack", package = "trackframe")
class(path_sftrack)
#> [1] "sftrack"    "sf"         "data.frame"
set.seed(2025L)
cpt_sftrack <- change_point_test(path_sftrack, alpha = 0.05, q = 3, n = 500, min_move_dist = 0)
summary(cpt_sftrack)
#>                  first                last          east       north
#> 1  2025-10-15 00:22:34 2025-10-15 00:22:34 -0.0072061100 0.003763871
#> 2  2025-10-15 13:43:34 2025-10-15 13:43:34 -0.0027773379 0.011703586
#> 3  2025-10-15 18:00:34 2025-10-15 18:07:34  0.0002694329 0.009003683
#> 4  2025-10-15 18:49:34 2025-10-15 18:49:34  0.0013820211 0.017076390
#> 5  2025-10-15 20:58:34 2025-10-15 20:58:34 -0.0049726251 0.016002070
#> 6  2025-10-15 23:43:34 2025-10-15 23:43:34  0.0005152087 0.015387244
#> 7  2025-10-16 02:39:34 2025-10-16 02:39:34 -0.0059711263 0.017819524
#> 8  2025-10-16 04:09:34 2025-10-16 04:09:34 -0.0038805680 0.014830813
#> 9  2025-10-16 07:16:34 2025-10-16 07:16:34 -0.0048342032 0.021118660
#> 10 2025-10-16 15:45:34 2025-10-16 15:45:34 -0.0007020970 0.011519583
#> 11 2025-10-16 16:17:34 2025-10-16 16:17:34 -0.0040944078 0.011046036
#> 12 2025-10-16 18:39:34 2025-10-16 18:39:34  0.0005950734 0.007295949
#> 13 2025-10-16 21:48:34 2025-10-16 21:48:34 -0.0050339505 0.004651530
#> 14 2025-10-16 22:02:34 2025-10-16 22:29:34 -0.0031042727 0.008906007
#> 15 2025-10-17 04:37:34 2025-10-17 04:37:34 -0.0053506489 0.005985680