Skip to contents

Derive heading angle(s) from trajectories using specified rule

Usage

derive_headings(x, rule, ..., coords = c("absolute", "relative"))

# S4 method for class 'Tracks'
derive_headings(
  x,
  rule,
  ...,
  first_only = FALSE,
  carry = NULL,
  on_missing = c("warn", "error", "quiet"),
  coords = c("absolute", "relative")
)

Arguments

x

Tracks

rule

Required. A single rule name, or a function. Built-in rules: `"crossing"`, `"distal"`, `"straight"`, `"origin_mean"`, `"net"`, `"velocity_mean"`, `"velocity_axis"`, `"window_net"`, `"goal_bias"`, `"pca_axis"`, `"ransac_straight"`, `"maxspeed_window"`, `"vm_fit"`, `"exit"`, `"entry"`, `"ring_tangent"`; a name registered with [register_heading_rule()] (see [list_heading_rules()]); or a function called once per trajectory as `f(df, cols, ...)` that returns columns `id`, `time` and `heading`.

...

rule-specific options (see Details), including `return_coords`. A built-in rule stops with an error naming any option it does not read; registered and function rules receive `...` unchecked.

coords

Character. Which Cartesian columns to use: `"absolute"` (default, uses whatever `x`/`y` the `Tracks` currently holds in `Tracks@cols` – calibrated only if the caller supplied or normalized them that way) or `"relative"` (uses `rel_x`/`rel_y`, the landmark-rotated frame; errors if not registered).

first_only

logical; if TRUE, return only the first matching heading per trajectory

carry

optional character vector of columns from the source data to append via nearest time

on_missing

One of `"warn"` (default), `"error"`, or `"quiet"`, controlling what happens when a rule produces no heading (`NA`) for one or more trials. The `NA` rows are always retained; the returned object carries `n_total` and `n_missing` (headings, i.e. rows), `n_trials` (distinct trials) and `missing_ids` (the trials with at least one missing heading) attributes. `"warn"` emits a warning, `"error"` stops, `"quiet"` is silent. Rule-based failures are often non-random (e.g. tracks that never reach the circumference) and can bias circular statistics, so they are surfaced by default.

Value

data.frame with columns id, time (approx), heading (radians, unit-circle convention), plus the rule-specific construction columns above when `return_coords = TRUE`. For some rules there may be multiple headings per id.

Details

Passing `return_coords = TRUE` (via `...`, default `FALSE`) attaches the construction coordinates each rule used to derive the heading, in the chosen `coords` frame: `crossing` adds `x_inner`/`y_inner`; `distal` adds `x_distal`/`y_distal`; `net` adds `x_start`/`y_start`/`x_end`/`y_end`; `straight` adds `x_seg0`/`y_seg0`/`x_seg1`/`y_seg1` (the run endpoints); `pca_axis` adds `x_centroid`/`y_centroid`/`axis_x`/`axis_y` (a unit axis vector). Other rules ignore it. The `distal` rule accepts `max_radius` (default `Inf`): the furthest-point search is restricted to positions with radius `<= max_radius`, so `max_radius = 1` ignores beyond-circumference (`rho > 1`) tracking outliers. The `straight` rule takes `tol` (default `pi/18`, the largest turn between successive steps) and `min_len` (default `5`, the fewest steps in a run). A step of zero length (a pause) has no direction: turns are measured between successive moving steps, so a pause neither breaks a run nor counts toward `min_len`. The heading is the direction of the run's first moving step. `pca_axis` takes `source = "position"` (default; the principal axis of the positions about their centroid) or `"velocity"` (the principal axis of the steps about zero, which weights a step by its squared length; compare `velocity_axis`, which weights by length).

The rules that average directions take a weighting through ...:

  • velocity_mean and vm_fit average the directions of the steps of a track. weight_by = "uniform" (default) counts every step once; "step_length" weights a step by its length, which gives the direction from the first position to the last, as net does. The two rules return the same heading: the maximum-likelihood mean direction of a von Mises distribution is the mean direction.

  • velocity_axis takes the same weight_by, with "step_length" as its default.

  • origin_mean averages the bearings of the positions from the origin, weighted by radius to the power r_power (default 0, every position counted once; 1 gives the bearing of the centroid).

A step of zero length and a position at the origin have no direction and are left out of these averages, whatever the weighting. A track with no direction left, or whose directions cancel, has heading NA.

The other rules likewise give NA where a displacement has zero length: a track that never moves (net, straight, goal_bias, maxspeed_window, window_net), one that ends where it started (net), a window whose ends coincide (window_net), positions with no spread, or steps none of which moves (pca_axis), a pca_axis track whose net displacement has no component along its axis (the axis then has no sign), and a track that stays at the origin (distal). window_net and maxspeed_window shrink their window to fit a track shorter than window_n.

Examples

h <- derive_headings(cpunctatus, rule = "distal")
head(h)
#> # A tibble: 6 × 3
#>   id       time heading
#>   <chr>   <int>   <dbl>
#> 1 10_10_1   299    4.77
#> 2 10_11_1    78    1.41
#> 3 10_12_1    49    2.25
#> 4 10_13_1   101    4.73
#> 5 10_14_1    30    1.94
#> 6 10_15_1    55    5.03
# ignore tracking outliers beyond the circumference and keep the point used
head(derive_headings(cpunctatus, rule = "distal", max_radius = 1,
                     return_coords = TRUE))
#> # A tibble: 6 × 5
#>   id       time heading x_distal y_distal
#>   <chr>   <int>   <dbl>    <dbl>    <dbl>
#> 1 10_10_1   299    4.77   0.0545   -0.880
#> 2 10_11_1    77    1.41   0.155     0.974
#> 3 10_12_1    45    2.25  -0.612     0.755
#> 4 10_13_1    99    4.73   0.0150   -0.997
#> 5 10_14_1    30    1.94  -0.356     0.907
#> 6 10_15_1    51    4.96   0.238    -0.961
# per-trial reference frame, carrying a condition column for grouping
head(derive_headings(cpunctatus, rule = "net", coords = "relative",
                     carry = "arc"))
#> # A tibble: 6 × 4
#>   id       time heading arc  
#>   <chr>   <dbl>   <dbl> <ord>
#> 1 10_10_1 150      1.54 10   
#> 2 10_11_1  46      6.15 10   
#> 3 10_12_1  25.5    2.18 10   
#> 4 10_13_1  61.5    6.28 10   
#> 5 10_14_1  15.5    5.24 10   
#> 6 10_15_1  28.5    3.41 10