Skip to contents
if (requireNamespace("pkgload", quietly = TRUE)) {
  pkgload::load_all("..", export_all = FALSE, helpers = FALSE, quiet = TRUE)
} else if (requireNamespace("radiatR", quietly = TRUE)) {
  library(radiatR)
} else {
  stop("Package 'radiatR' not installed and 'pkgload' not available.")
}
library(ggplot2)

Overview

simulate_tracks() generates synthetic circular-arena trajectories without any tracking files. Its main uses are:

  • Verifying that a new analysis pipeline works before real data arrives.
  • Reproducing plots and examples in documentation.
  • Teaching: demonstrating how concentration, tortuosity, and directional bias each affect the appearance of paths.
  • Informal power analysis: generate data under a plausible effect size and check whether the expected statistical pattern is visible.

Quick Start

Called with no arguments, simulate_tracks() returns a tibble of three conditions (60 trials total, 200 frames each):

sim <- simulate_tracks(seed = 1)
dim(sim)
#> [1] 12000    16
names(sim)
#>  [1] "condition"     "trial_id"      "trial_num"     "frame"        
#>  [5] "predictor"     "concentration" "tortuosity"    "ref_heading"  
#>  [9] "final_heading" "rho"           "abs_theta"     "rel_theta"    
#> [13] "abs_x"         "abs_y"         "rel_x"         "rel_y"

Each row is one frame. The key columns are:

Column Description
condition Condition label
trial_id Unique trial identifier (<condition>_<index>)
frame Frame number within the trial
predictor Per-trial continuous covariate value
concentration Effective von Mises κ for the trial
tortuosity Effective angular noise σ for the trial
final_heading Drawn heading (unit-circle radians)
abs_x, abs_y Absolute position on the unit disc
rel_x, rel_y Position re-centred on the heading direction
abs_theta, rel_theta Corresponding angular coordinates

Output Formats

The output argument controls what is returned.

# Default: long-form tibble
sim_tbl  <- simulate_tracks(seed = 1, output = "tibble")

# TrajSet directly (ready for radiate(), derive_headings(), etc.)
sim_ts   <- simulate_tracks(seed = 1, output = "trajset")

# Both representations in a named list
sim_both <- simulate_tracks(seed = 1, output = "both")
names(sim_both)  # "tibble" "trajset"

The output = "trajset" path wraps the tibble in a TrajSet (absolute coordinates, no normalisation). Use it whenever you want to plug straight into the plotting or analysis functions.


Plotting the Default Simulation

ts_default <- simulate_tracks(seed = 1, output = "trajset")
radiate(ts_default,
        group_col    = "trial_id",
        colour_cycle = 10,
        panel_by     = "condition",
        ncol         = 3,
        show_labels  = FALSE,
        show_arrow   = TRUE)

The three default conditions differ in directional bias, concentration, and tortuosity (see below).


The Conditions Table

All simulation behaviour is controlled by a data frame passed to conditions. When conditions = NULL the function fills in a three-condition template. Supply your own data frame to override any subset of columns; missing columns receive sensible defaults.

Column Default Description
condition "condition_N" Label for the condition
n_trials 10 Number of trajectories
ref_mean 0 Baseline reference direction (unit-circle radians)
concentration_base 5 Baseline von Mises κ
concentration_slope 0 Slope of κ on the per-trial predictor
tortuosity_base 0.06 Baseline angular noise σ
tortuosity_slope 0 Slope of σ on the predictor
tortuosity_sd 0.01 Trial-to-trial variability in σ
predictor_mean 0 Mean of the per-trial predictor distribution
predictor_sd 0.2 SD of the predictor distribution
predictor_values (none) Optional list-column of explicit predictor values

Concentration (κ)

concentration_base is the von Mises κ passed to rvonmises(). Higher values produce tightly clustered headings; values near zero produce near-uniform heading distributions.

conds_kappa <- data.frame(
  condition          = c("low (κ=0.1)", "medium (κ=1)", "high (κ=12)"),
  n_trials           = 20L,
  concentration_base = c(0.1, 1, 12),
  tortuosity_base    = 0.05
)
ts_kappa <- simulate_tracks(conditions = conds_kappa, seed = 42,
                             output = "trajset")
radiate(ts_kappa,
        group_col    = "trial_id",
        colour_cycle = 10,
        panel_by     = "condition",
        ncol         = 3,
        show_labels  = FALSE,
        show_arrow   = TRUE)

At κ = 1 the mean arrow is short (low resultant length); at κ = 12 nearly all trials point in the same direction.


Tortuosity and Path Smoothness

Two parameters control within-trial path shape:

  • tortuosity_base — the SD of the per-step angular noise. Larger values produce more sinuous paths.
  • phi — autocorrelation of successive angular deviations (0 = white noise, values near 1 produce smooth, sweeping curves). Passed directly to simulate_tracks(), not via the conditions table.
conds_tort <- data.frame(
  condition          = c("smooth (φ=0.95)", "moderate (φ=0.7)", "noisy (φ=0)"),
  n_trials           = 15L,
  concentration_base = 6,
  tortuosity_base    = c(0.12, 0.12, 0.12)
)
phi_vals <- c(0.95, 0.7, 0)

# Simulate each phi separately and bind
parts <- lapply(seq_along(phi_vals), function(i) {
  d <- conds_tort[i, , drop = FALSE]
  simulate_tracks(conditions = d, n_points = 200,
                  phi = phi_vals[i], seed = 7 + i)
})
ts_tort <- TrajSet(
  do.call(rbind, parts),
  id = "trial_id", time = "frame",
  x = "abs_x", y = "abs_y", angle = "abs_theta",
  angle_unit = "radians", normalize_xy = FALSE
)
radiate(ts_tort,
        group_col    = "trial_id",
        colour_cycle = 10,
        panel_by     = "condition",
        ncol         = 3,
        show_labels  = FALSE,
        show_arrow   = FALSE)


Directional Bias (ref_mean)

ref_mean shifts the reference direction away from East (0 rad). Use it to simulate experiments where the reference is not along the positive x-axis.

conds_bias <- data.frame(
  condition          = c("East (0°)", "North (90°)", "Northwest (135°)"),
  n_trials           = 20L,
  concentration_base = 5,
  tortuosity_base    = 0.06,
  ref_mean          = c(0, pi / 2, 3 * pi / 4)
)
ts_bias <- simulate_tracks(conditions = conds_bias, seed = 99,
                           output = "trajset")
radiate(ts_bias,
        group_col    = "trial_id",
        colour_cycle = 10,
        panel_by     = "condition",
        ncol         = 3,
        show_labels  = FALSE,
        show_arrow   = TRUE)


Multi-Condition Gradient

A common design is a monotone gradient in cue strength. The example below simulates four levels with increasing concentration and decreasing tortuosity — mimicking an orientation experiment where stronger cues produce tighter, smoother paths.

conds_grad <- data.frame(
  condition          = paste0(c(15, 30, 60, 120), "° arc"),
  n_trials           = 25L,
  concentration_base = c(1.5, 3, 6, 10),
  tortuosity_base    = c(0.14, 0.10, 0.06, 0.03),
  tortuosity_sd      = 0.015
)
ts_grad <- simulate_tracks(conditions = conds_grad, n_points = 200,
                            seed = 55, output = "trajset")
radiate(ts_grad,
        group_col    = "trial_id",
        colour_col   = "condition",
        panel_by     = "condition",
        ncol         = 4,
        show_labels  = FALSE,
        show_arrow   = TRUE)


Continuous Predictor

When concentration_slope or tortuosity_slope is non-zero, the final kappa and tortuosity of each trial depend on its sampled predictor value. This lets you model experiments where individual differences (e.g. body size, prior exposure) modulate the response.

conds_pred <- data.frame(
  condition            = "gradient",
  n_trials             = 40L,
  concentration_base   = 3,
  concentration_slope  = 4,    # kappa rises with predictor
  tortuosity_base      = 0.10,
  tortuosity_slope     = -0.04, # smoother at high predictor
  predictor_mean       = 0,
  predictor_sd         = 1
)
sim_pred <- simulate_tracks(conditions = conds_pred, seed = 7, output = "both")

# Each trial gets its own predictor sample
range(sim_pred$tibble$predictor)
#> [1] -1.387996  2.716752
range(sim_pred$tibble$concentration)
#> [1]  0.10000 13.86701

Plotting concentration against the predictor confirms the linear relationship:

trial_summary <- unique(sim_pred$tibble[, c("trial_id", "predictor",
                                             "concentration")])
ggplot(trial_summary, aes(predictor, concentration)) +
  geom_point(alpha = 0.5) +
  geom_smooth(method = "lm", se = FALSE, colour = "steelblue") +
  labs(x = "Predictor", y = "Effective κ") +
  theme_minimal()
#> `geom_smooth()` using formula = 'y ~ x'


Connecting to Circular Analysis

Simulated TrajSet objects feed directly into derive_headings() and circ_summarise(), making it straightforward to run the same analysis pipeline on synthetic data.

conds_analysis <- data.frame(
  condition          = c("diffuse", "concentrated"),
  n_trials           = 30L,
  concentration_base = c(1.5, 8),
  tortuosity_base    = c(0.12, 0.04)
)
sim_analysis <- simulate_tracks(conditions = conds_analysis, seed = 21,
                                 output = "both")

hd <- derive_headings(sim_analysis$trajset, rule = "crossing",
                       circ0 = 0.2, circ1 = 0.4,
                       coords = "absolute",
                       angle_convention = "unit_circle",
                       return_coords = TRUE)
names(hd)[names(hd) == "id"] <- "trial_id"

# derive_headings returns only id/heading/coords; join condition back
cond_map <- unique(sim_analysis$tibble[, c("trial_id", "condition")])
hd <- merge(hd, cond_map, by = "trial_id")

compute_circ_mean(hd, colour_col = "condition")[,
  c("condition", "mean_dir", "resultant_R")]
#>      condition   mean_dir resultant_R
#> 1 concentrated 0.05301978   0.9039690
#> 2      diffuse 0.29012285   0.7131608

Visualise the headings alongside the trajectories:

p <- radiate(sim_analysis$trajset,
             group_col    = "trial_id",
             colour_col   = "condition",
             panel_by     = "condition",
             ncol         = 2,
             show_labels  = FALSE,
             show_arrow   = FALSE) +
  add_heading_points(hd, colour_col = "condition", size = 2, alpha = 0.7) +
  add_heading_arrow(hd, colour_col = "condition", colour = "black")
p


Reproducibility and Saving

Pass seed for exact reproducibility. Pass write_path to write the tibble to a CSV alongside (or instead of) returning it — useful when you want to hand the data to a colleague or another tool.

simulate_tracks(
  conditions = conds_grad,
  n_points   = 200,
  seed       = 55,
  write_path = "sim_gradient.csv"
)