| Title: | Actigraphy Data Parsing and Analysis for R |
|---|---|
| Description: | Provides tools for importing, parsing, and analysing raw actigraphy data from wrist-worn devices. Implements a full sleep analysis pipeline — off-wrist detection, main sleep period detection, nap detection, and WASO computation — validated epoch-for-epoch against the Condor circadiaBase Python reference pipeline using ActTrust hardware. Also computes standard non-parametric circadian rhythm variables (interdaily stability, intradaily variability, relative amplitude, L5, M10). Device-specific parameter presets can be swapped to adapt the pipeline to other actigraph models. Designed to complement slumbR in the Circadia Lab ecosystem. |
| Authors: | Lucas França [aut, cre] (ORCID: <https://orcid.org/0000-0003-0853-1319>), Mario Leocadio-Miguel [aut] (ORCID: <https://orcid.org/0000-0002-7248-3529>), Julia Ribeiro da Silva Vallim [aut] (ORCID: <https://orcid.org/0000-0001-8708-8479>) |
| Maintainer: | Lucas França <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.1.8 |
| Built: | 2026-10-23 11:18:55 UTC |
| Source: | https://github.com/circadia-bio/zeitR |
Returns a named character vector of hex colours keyed on the four state
labels produced by label_states(). Pass the output to the colours
argument of any actogram function to override individual colours.
actogram_colours()actogram_colours()
Named character vector with elements "wake", "sleep", "nap",
and "off-wrist".
actogram_colours() # wake sleep nap off-wrist # "#C25E2A" "#3B2F6B" "#F0A500" "#D9C8A0"actogram_colours() # wake sleep nap off-wrist # "#C25E2A" "#3B2F6B" "#F0A500" "#D9C8A0"
Returns a named list of all device- and study-specific parameter values used
by the zeitR pipeline when processing ActTrust recordings. Passing this list
(or a modified copy) to run_pipeline() via the params argument fully
controls which values each detector stage receives, without having to touch
the individual function calls.
acttrust_params()acttrust_params()
The list is organised into four named sections that map directly to the four pipeline stages:
offwristParameters for detect_offwrist_bimodal(). These values
were validated against the Condor circadiaBase pipeline using ActTrust
hardware. The refinement stage (.bimodal_refine_acttrust) is
device-specific and currently only validated for ActTrust.
sleepParameters for detect_sleep_crespo(). sleep_quantile
(0.365) is the ActTrust CSPD wrapper value; the original Crespo (2012)
algorithm uses 1/3.
napParameters for detect_naps_crespo(), passed as the params
argument. Equivalent to .cspd_nap_params().
wasoParameters for compute_waso().
To adapt the pipeline for a different device, copy the list, modify the
relevant values, and pass it to run_pipeline():
p <- acttrust_params()
p$sleep$sleep_quantile <- 1/3 # use the original Crespo threshold
result <- run_pipeline("recording.txt", params = p)
A named list with elements offwrist, sleep, nap, and waso.
run_pipeline() for the pipeline entry point.
Scans a recording tibble for three classes of timestamp problem:
check_consistency(x, gap_s = 120, datetime_col = "datetime")check_consistency(x, gap_s = 120, datetime_col = "datetime")
x |
A tibble as returned by |
gap_s |
|
datetime_col |
|
Gaps — intervals between consecutive epochs longer than gap_s
seconds.
Backward jumps — timestamps that go backwards in time.
Year artefacts — timestamps in the years 1970 or 2000, which typically indicate firmware epoch-counter rollover bugs.
A tibble with one row per detected issue and columns:
rowinteger — row index in x where the issue occurs.
datetimePOSIXct — timestamp at that row.
issuecharacter — one of "gap", "backward_jump", or
"year_artefact".
detailcharacter — human-readable description.
Returns a zero-row tibble if no issues are found.
## Not run: rec <- read_acttrust("recordings/P001.txt") issues <- check_consistency(rec) issues ## End(Not run)## Not run: rec <- read_acttrust("recordings/P001.txt") issues <- check_consistency(rec) issues ## End(Not run)
Computes the mean of times on the 0-24 h clock using the unit-circle
method: convert each time to a unit-vector angle, average the sin and
cos components, and back-project via atan2. This correctly handles
midnight wrap (e.g. times at 23:30 and 00:30 average to 00:00, not 12:00).
circ_mean_h(x)circ_mean_h(x)
x |
|
The older threshold approach (shift values >= 12 h by -24 h before averaging) produces badly biased estimates when times straddle noon – critically for sleep offset in late sleepers – and is superseded by this function (Fix 20 in the JRSV pipeline, ICC for sleep offset 0.502 -> 0.897 on N=404).
A single numeric in [0, 24), or NA_real_ if x is empty
after NA removal.
Pewsey, A., Neuhauser, M., & Ruxton, G. D. (2013). Circular Statistics in R. Oxford University Press.
circ_mean_h(c(23.5, 0.5)) # midnight wrap -> 0 circ_mean_h(c(23.0, 1.0)) # -> 0 circ_mean_h(c(7.0, 9.0)) # -> 8circ_mean_h(c(23.5, 0.5)) # midnight wrap -> 0 circ_mean_h(c(23.0, 1.0)) # -> 0 circ_mean_h(c(7.0, 9.0)) # -> 8
Computes the circular SD using the mean resultant length formula:
sqrt(-2 * log(R_bar)) * 24 / (2*pi), where R_bar is the mean
resultant length of the unit-circle representation of the times.
Linear SD inflates when times straddle midnight; this measure is
invariant to the wrap point (Fix 24 in the JRSV pipeline).
circ_sd_h(x)circ_sd_h(x)
x |
|
A single non-negative numeric (hours), or NA_real_.
Mardia, K. V., & Jupp, P. E. (2000). Directional Statistics (2nd ed.). Wiley.
circ_sd_h(c(23.5, 0.5)) # small SD for times close to midnight circ_sd_h(c(6.0, 18.0)) # large SD for times 12 h apartcirc_sd_h(c(23.5, 0.5)) # small SD for times close to midnight circ_sd_h(c(6.0, 18.0)) # large SD for times 12 h apart
Convenience wrapper combining estimate_ee() and classify_pa_intensity()
in one call: converts raw ActTrust(R)/GT3X+ activity counts straight to a PA
intensity factor via the published "batista2026" (or future) equation
set, without needing to handle the intermediate MET values.
classify_pa_counts( counts, device = c("ACTT", "GT3X+"), placement = c("hip", "wrist"), equation_set = "batista2026" )classify_pa_counts( counts, device = c("ACTT", "GT3X+"), placement = c("hip", "wrist"), equation_set = "batista2026" )
counts |
Numeric vector of activity counts (counts/min, over the same
epoch length used by the source equation set – 1-minute for
|
device |
|
placement |
|
equation_set |
|
An ordered factor, same length as counts and same levels as
classify_pa_intensity().
estimate_ee(), classify_pa_intensity(), pa_equations() for
the coefficients and their generalisability caveats.
classify_pa_counts(c(0, 5000, 25000, 50000), device = "ACTT", placement = "hip")classify_pa_counts(c(0, 5000, 25000, 50000), device = "ACTT", placement = "hip")
Bins metabolic equivalent (MET) values into the four PA intensity classes used throughout Batista et al. (2026) and consistent with WHO PA intensity conventions: light, moderate, vigorous, very vigorous.
classify_pa_intensity(mets)classify_pa_intensity(mets)
mets |
Numeric vector of MET values, e.g. from |
An ordered factor with levels c("light", "moderate", "vigorous", "very_vigorous"), same length as mets. Bands: [0,3) light,
[3,6) moderate, [6,9) vigorous, [9,Inf) very vigorous.
mets <- estimate_ee(c(0, 5000, 25000, 50000), device = "ACTT", placement = "hip") classify_pa_intensity(mets)mets <- estimate_ee(c(0, 5000, 25000, 50000), device = "ACTT", placement = "hip") classify_pa_intensity(mets)
Applies the JRSV classification rule set to a raw episode table from
extract_sleep_episodes(), assigning each episode a sleep_type of
"main" or "secondary". The execution order follows Fix 27:
classify_sleep_episodes( episodes, data, max_tib_h = 16, max_main_tib_h = 14, min_main_tib_h = 4, nocturnal_onset_start = 18, nocturnal_onset_end = 6, temp_thresh = 28, light_thresh_window = 5, light_thresh_recovery = 10, min_fragment_tib_h = 1, rolling_window_min = 15L, max_split_iterations = 3L, collision_gap_h = 4, verbose = FALSE )classify_sleep_episodes( episodes, data, max_tib_h = 16, max_main_tib_h = 14, min_main_tib_h = 4, nocturnal_onset_start = 18, nocturnal_onset_end = 6, temp_thresh = 28, light_thresh_window = 5, light_thresh_recovery = 10, min_fragment_tib_h = 1, rolling_window_min = 15L, max_split_iterations = 3L, collision_gap_h = 4, verbose = FALSE )
episodes |
A tibble as returned by |
data |
A tibble as returned by |
max_tib_h |
|
max_main_tib_h |
|
min_main_tib_h |
|
nocturnal_onset_start |
|
nocturnal_onset_end |
|
temp_thresh |
|
light_thresh_window |
|
light_thresh_recovery |
|
min_fragment_tib_h |
|
rolling_window_min |
|
max_split_iterations |
|
collision_gap_h |
|
verbose |
|
Fix 29f – exclude episodes starting at/after noon on the
latest classified night's calendar date (max(gts)), before any
other classification step. Prevents a spurious end-of-recording
artifact (device removed mid-day, not a real night) from being
counted as a valid main night.
Fix 26a – infer adaptive nocturnal window from int_temp
and light. Falls back to nocturnal_onset_start/nocturnal_onset_end
if fewer than 2 candidate episodes pass the temperature and light filter.
Fix 29 / Rule 1 – split TBT 14–16 h episodes at their
activity peak (recursive, up to max_split_iterations). Fragments still
exceeding max_main_tib_h after splitting are excluded.
Rule 2 – exclude episodes with TBT > max_tib_h (16 h)
directly, without attempting a split.
Fix 26c – recover sleep nights missed by the scorer for dates
with no classified main episode. Sleep and wake within the candidate
window are determined by Cole-Kripke epoch scoring on ZCMn (not the
period-level CSPD state). Adjacent sleep runs are merged when the
intervening gap has wrist temperature >= temp_thresh and ambient
light <= light_thresh_recovery.
Rules 3–5 – classify each episode as "main" or
"secondary" using the nocturnal window and min_main_tib_h.
Fix 26b – resolve sleep-date collisions: when two main
episodes share a noon-threshold sleep date and are separated by
>= collision_gap_h hours, reassign the later one to the next calendar date.
Rule 6 – keep the longest main episode per sleep date;
demote all others to "secondary".
Rule 7 – exclude all episodes (main and secondary) on dates that have no valid main sleep after Rule 6.
A tibble with the same columns as episodes plus sleep_type
("main" or "secondary") and is_nap (logical, TRUE when
sleep_type == "secondary", for backwards compatibility with the
zeitr_result$nights schema).
extract_sleep_episodes(), run_pipeline_native()
Reproduces the general wrist-actigraph processing chain – per-axis
DC removal and band-pass filter, vector norm, epoch-level integration –
that converts raw acceleration samples into the epoch-level activity
metrics used throughout the rest of zeitR (e.g. read_acttrust()'s
activity/ZCMn columns) and the wider PA-intensity literature (see
pa_equations()).
compute_activity_counts( x, y, z, sampling_rate, epoch_sec = 60, filter_low = 0.5, filter_high = 2.7, zcm_threshold = 0.01, tat_threshold = 0.05, metrics = c("PIM", "TAT", "ZCM") )compute_activity_counts( x, y, z, sampling_rate, epoch_sec = 60, filter_low = 0.5, filter_high = 2.7, zcm_threshold = 0.01, tat_threshold = 0.05, metrics = c("PIM", "TAT", "ZCM") )
x, y, z
|
Numeric vectors of raw triaxial acceleration samples (same
units – typically g – and length, uniformly sampled at
|
sampling_rate |
|
epoch_sec |
|
filter_low, filter_high
|
|
zcm_threshold |
|
tat_threshold |
|
metrics |
|
A tibble with one row per epoch: epoch (integer index, 1-based)
plus one column per requested metric.
Filtering reuses mrpheus::remove_dc() and mrpheus::bandpass_filter()
– the same zero-phase Butterworth implementation already validated as
part of mrpheus's YASA-parity PSG pipeline – rather than a new,
unvalidated filter written from scratch. This makes mrpheus a required
package for this function specifically (a Suggests dependency of
zeitR, checked at runtime; the rest of zeitR does not need it).
Reusing a validated filter primitive does not make the whole function
validated, though: the PIM/TAT/ZCM epoch-aggregation logic below (what
happens after filtering) is still an original implementation with
nothing to check it against – no raw-to-counts converter exists to
compare it to, and Condor's/ActiGraph's exact onboard threshold constants
are proprietary and unpublished. Built from the general processing
description in Batista et al. (2026, PLoS ONE) and standard actigraphy
literature. Treat its output as a reasonable approximation of
device-computed counts, not a validated reproduction – and prefer
device-computed counts (read_acttrust()) over this function whenever
they're available.
Each of x, y, z has its DC offset removed
(mrpheus::remove_dc()) – important for the z-axis in particular,
which typically carries a ~1 g gravity offset – then is independently
band-pass filtered with a zero-phase (mrpheus::bandpass_filter())
Butterworth filter (filter_low-filter_high Hz).
A vector norm is computed per sample: sqrt(x^2 + y^2 + z^2) on the
filtered axes, matching the order described for ActTrust in Batista
et al. (2026): filter first, then combine axes.
Samples are grouped into non-overlapping epochs of
epoch_sec * sampling_rate samples. Trailing samples that don't fill
a complete final epoch are dropped, with a warning.
Per epoch:
PIM (proportional integration mode) – sum of the absolute filtered norm across the epoch.
TAT (time above threshold) – seconds within the epoch where the
absolute filtered norm exceeds tat_threshold.
ZCM (zero crossing mode) – number of sign changes beyond a
zcm_threshold dead-band, summed across the three filtered axes,
within the epoch.
zcm_threshold and tat_threshold have no published reference value –
they are exposed as tunable parameters rather than given a false-precision
default. The values shipped here are small, physically motivated starting
points (see Arguments), not calibrated constants.
pa_equations(), estimate_ee(), classify_pa_counts() to go
from PIM counts to METs and PA intensity bands; read_acttrust() for
device-computed counts (preferred over this function when available);
mrpheus::bandpass_filter(), mrpheus::remove_dc() for the underlying
filter primitives.
set.seed(1) n <- 25 * 60 * 5 # 5 minutes at 25 Hz x <- rnorm(n, sd = 0.05) y <- rnorm(n, sd = 0.05) z <- rnorm(n, sd = 0.05) + 1 # gravity on the z-axis ## Not run: compute_activity_counts(x, y, z, sampling_rate = 25, epoch_sec = 60) ## End(Not run)set.seed(1) n <- 25 * 60 * 5 # 5 minutes at 25 Hz x <- rnorm(n, sd = 0.05) y <- rnorm(n, sd = 0.05) z <- rnorm(n, sd = 0.05) + 1 # gravity on the z-axis ## Not run: compute_activity_counts(x, y, z, sampling_rate = 25, epoch_sec = 60) ## End(Not run)
Fits a single-harmonic cosine model to an activity/light/temperature signal with a FIXED period (default 24h), returning the acrophase (peak time), MESOR (rhythm-adjusted mean), and amplitude.
compute_cosinor(x, col = "activity", period_min = 1440)compute_cosinor(x, col = "activity", period_min = 1440)
x |
A |
col |
|
period_min |
|
Ports pyActigraphy's actual Cosinor class (pyActigraphy/analysis/ cosinor.py) as it is really used in the reference notebook (Cell 16 of
vs_condor_py_pipeline_fix30_jrsv.ipynb's _fit_cosinor()), not
reconstructed from documentation. The model is
with , the integer epoch position
(0, 1, 2, ..., matching Cosinor._convert_timestamp_to_index()'s
(index - index[0]) / index.freq, not clock time or elapsed seconds),
the MESOR, the amplitude, the acrophase.
Cell 16's wrapper explicitly locks Period (vary=False) rather than
fitting it – its own comment explains why: leaving Period free let
the optimizer converge anywhere from 0.23h to 91.75h, making the
acrophase/period meaningless. With the period fixed, the model is
linear in disguise: expanding
and substituting ,
gives , an
ordinary linear regression in – solved here
via qr.solve(), not a nonlinear optimizer. Verified by direct
execution that this closed-form OLS solution matches pyActigraphy's
actual lmfit (Levenberg-Marquardt) fit to within floating-point
precision when the period is locked, which it always is in the
notebook's actual usage – this is not an approximation.
acrophase_time/acrophase_time_neg reproduce the notebook's own
sign-flip fix exactly: the model's peak occurs at
t_peak = -phi / omega = -phi * T / (2*pi), minutes since the first
epoch – the notebook's own comment documents a prior bug where the
unnegated +phi * T/(2*pi) gave the ANTI-peak (trough), offset ~12h
from the true peak. t_peak is then wrapped to a 24h clock time via
round(t_peak) %% 1440 – hardcoded to 1440 regardless of the actual
period_min used for the fit, matching the source exactly (the
wrap-to-clock-time step assumes a 24h day, since it's meant to report a
wall-clock peak time, not a literal fit-period position).
acrophase_time_neg maps this to [-12, 12] (values >= 12:00 shifted
by -24), matching the _om10_neg/_ol5_neg convention used elsewhere
in the same notebook for circular-safe comparison of clock times.
A tibble with columns participant_id, acrophase_time
("HH:MM" string), acrophase_time_neg (numeric, [-12, 12]),
MESOR, amplitude, period_min, n_epochs.
Cornelissen, G. (2014). Cosinor-based rhythmometry. Theoretical Biology and Medical Modelling, 11(1), 16. doi:10.1186/1742-4682-11-16
compute_npcra(), compute_sri()
## Not run: result <- run_pipeline_native("recordings/P001.txt", tz = "America/Sao_Paulo") compute_cosinor(result) compute_cosinor(result, col = "light") ## End(Not run)## Not run: result <- run_pipeline_native("recordings/P001.txt", tz = "America/Sao_Paulo") compute_cosinor(result) compute_cosinor(result, col = "light") ## End(Not run)
Calculates chronobiological phenotyping metrics from classified nightly sleep
data. Mirrors compute_cpd_metrics() and nights_to_cpd_df() from Julia
Ribeiro da Silva Vallim's pipeline_functions_fix27.py, with two updates
to match the fix29 notebook:
MSF and MSW are computed with the circular mean to correctly handle mid-sleep times that wrap midnight.
Truncated episodes starting after noon on the last recording day are excluded before metric computation.
compute_cpd_metrics(x, ...) ## S3 method for class 'zeitr_result' compute_cpd_metrics( x, min_tib_h = 3, min_tib_eve_h = 3, tz = "UTC", holidays = x$holidays, free_days = x$free_days, ... ) ## Default S3 method: compute_cpd_metrics( x, min_tib_h = 3, min_tib_eve_h = 3, tz = "UTC", holidays = NULL, free_days = c("Saturday", "Sunday"), ... )compute_cpd_metrics(x, ...) ## S3 method for class 'zeitr_result' compute_cpd_metrics( x, min_tib_h = 3, min_tib_eve_h = 3, tz = "UTC", holidays = x$holidays, free_days = x$free_days, ... ) ## Default S3 method: compute_cpd_metrics( x, min_tib_h = 3, min_tib_eve_h = 3, tz = "UTC", holidays = NULL, free_days = c("Saturday", "Sunday"), ... )
x |
A |
... |
Not used; reserved for forward compatibility with future methods. |
min_tib_h |
|
min_tib_eve_h |
|
tz |
|
holidays |
Holidays to treat as free days in addition to the days in
|
free_days |
A character vector of day names ( |
Dispatches on the class of x:
data.frame / tibble – treated as a nights table (same behaviour
as the original function signature).
zeitr_result – extracts x$nights and inherits x$holidays and
x$free_days automatically.
Mid-sleep is computed per night as:
where onset = bts + SOL and offset = gts - SOI (both in decimal hours).
MSW and MSF use the circular mean of per-night mid-sleep values,
treating clock hours as angles on a 24-hour circle so values near
midnight from opposite sides average correctly, matching calculate_msf()
/ calculate_msw() from the fix29 notebook.
MSFsc adjusts MSF by the free-day-eve sleep onset and the weighted
weekly mean sleep duration when free-day duration exceeds weekday duration.
CPD is the RMS distance of each night's mid-sleep from MSFsc in the
time x sequence plane.
A named list with n_nights_cpd, n_free_days, n_workdays,
msw_h, msw_hms, msf_h, msf_hms, msfsc_h, msfsc_hms,
sjl_h, sjl_min, sjla_h, sjla_min, cpd_s, cpd_min, cpd_h.
compute_sleep_metrics(), run_pipeline_native()
The main entry point for the LIDS pipeline: extracts sleep bouts, applies
lids_transform() and fit_lids() to each, and returns one row per bout
with cosine-fit parameters and a quality-filter flag. Ports the full
pipeline described in Winnebeck et al. (2018) and Hammad et al. (2026).
compute_lids( x, bout_source = c("auto", "state", "roenneberg"), activity_col = "activity", method = c("gaussian", "mva"), win_min = 30, sigma_min = 5, period_range = c(30, 180), period_step = 2, duration_range = c(3, 12), min_r = 0.4, max_p = 0.05, offset_bounds = c(1, 99), bout_args = list() )compute_lids( x, bout_source = c("auto", "state", "roenneberg"), activity_col = "activity", method = c("gaussian", "mva"), win_min = 30, sigma_min = 5, period_range = c(30, 180), period_step = 2, duration_range = c(3, 12), min_r = 0.4, max_p = 0.05, offset_bounds = c(1, 99), bout_args = list() )
x |
A |
bout_source |
|
activity_col |
|
method, win_min, sigma_min
|
Forwarded to |
period_range, period_step
|
Forwarded to |
duration_range |
|
min_r, max_p, offset_bounds
|
Quality-filter thresholds; see Details. |
bout_args |
Named list of additional arguments forwarded to
|
A tibble with one row per bout: participant_id, bout_id,
bout_start, bout_end, duration_h, period_min, amplitude,
offset, slope_per_60min, phase_rad, pearson_r, p_value, mri,
passes_quality_filter.
bout_source)"state" – uses the epoch-level state column already produced by
zeitR's own pipelines (run_pipeline() / run_pipeline_native()):
contiguous state == 1 runs are treated as bouts, filtered by
duration_range. Off-wrist (state == 4) epochs break a run rather
than being bridged.
"roenneberg" – ignores any existing state column and runs the
independent detect_lids_bouts() relative-immobility detector directly
on the activity signal. Use this for standalone recordings that haven't
been run through zeitR's Crespo/Vallim pipelines, or to reproduce
Winnebeck/Hammad's own bout-detection method rather than zeitR's.
"auto" (the default) – "state" if a state column is present in
x, otherwise "roenneberg".
Following Winnebeck et al. (2018) and Hammad et al. (2026), a bout passes quality filtering when all of:
pearson_r >= min_r (default 0.4 – a soft data-quality threshold,
not a hard significance test; ~75% of adult bouts cleared this bar in
Winnebeck et al. 2018),
p_value <= max_p (default 0.05),
offset_bounds[1] < offset < offset_bounds[2] (default 1 < offset < 99), excluding spuriously flat bouts (e.g. a lost/removed device).
Bouts failing quality filtering are still returned (with
passes_quality_filter = FALSE) rather than dropped, so callers can
inspect what was excluded.
Winnebeck, E. C., Fischer, D., Leise, T., & Roenneberg, T. (2018). Dynamics and Ultradian Structure of Human Sleep in Real Life. Current Biology, 28(1), 49-59.e5. doi:10.1016/j.cub.2017.11.063
Hammad, G., Schoch, S. F., Engelmann, M., Spock, Z., Kurth, S., & Winnebeck, E. C. (2026). Charting infant sleep cycle development using actigraphy: Longitudinal evidence for ultradian cycle lengthening within the first year of life. SLEEP.
lids_transform(), fit_lids(), detect_lids_bouts(),
study_lids_metrics()
## Not run: result <- run_pipeline_native("recordings/P001.txt", tz = "America/Sao_Paulo") compute_lids(result) ## End(Not run)## Not run: result <- run_pipeline_native("recordings/P001.txt", tz = "America/Sao_Paulo") compute_lids(result) ## End(Not run)
Computes the Light Regularity Index (Hand et al. 2023): a measure of day-to-day consistency in light exposure timing, analogous to the Sleep Regularity Index but applied to a binarized light-exposure signal.
compute_lri(x, col = "light", threshold = NULL, log_transform = FALSE)compute_lri(x, col = "light", threshold = NULL, log_transform = FALSE)
x |
A |
col |
|
threshold |
|
log_transform |
|
Ports the artvalencio/pyActigraphy fork's actual LRI() method
(pyActigraphy/light/light_metrics.py) exactly – found there after an
earlier investigation confirmed it does not exist anywhere in the
official ghammad/pyActigraphy repository (any branch, any commit in
history). LRI()'s own inner prob_stability()/lri_profile()
functions are structurally identical to compute_sri()'s "sadeh"/
"ck"/"scripps"/"roenneberg" paths' aggregation
(.sri_pyactigraphy()) – verified by direct execution that LRI()'s
own code and a direct call to sri() (pyActigraphy/sleep/scoring/ sri.py) give IDENTICAL results on the same real light data, to full
floating-point precision. compute_lri() reuses .sri_pyactigraphy()
directly rather than duplicating the same formula under a different
name.
A real discrepancy worth knowing about, not a guess: the
reference notebook's own comment (Cell 16 of
vs_condor_py_pipeline_fix30_jrsv.ipynb) claims "pyActigraphy stores
light data in log10-transformed lux internally," and picks its default
thresholds accordingly (10/20/50/100/300 lux -> log10 -> 1.0/1.301/1.699/2.0/2.4771). This is not what the code actually does:
read directly, RawATR's constructor
(pyActigraphy/io/atr/atr.py) passes the LIGHT column straight into
LightRecording with no log10() call anywhere, and real light values
from actual ActTrust recordings confirm this empirically – raw LIGHT
ranges from 0 to over 20,000 in real data checked this session, nowhere
near the ~0-5 range log10(lux) would produce. So the reference
Python's own LRI_10/LRI_20/etc. columns almost certainly don't
measure what their names claim – thresholds of ~1-2.5 applied to raw,
untransformed light amount to "any detectable light vs. darkness," not
"50 lux vs. below." This function replicates the reference's actual
behaviour (raw threshold, no transform) for parity purposes – matching
what the real pipeline computes, not what its comments say it computes.
log_transform = TRUE is available if you want the – probably
originally intended – log10(lux) behaviour instead; it is NOT the
default, since the default should match the real reference output.
A tibble with participant_id, either LRI_10, LRI_20,
LRI_50, LRI_100, LRI_300 (default, threshold = NULL) or a
single LRI column (custom threshold), and n_epochs.
Hand, A. J., Lane, J. M., Payne, A. C., ... & Barker, D. H. (2023). Measuring light regularity: sleep regularity is associated with regularity of light exposure in adolescents. Sleep, 46(8), zsad001. doi:10.1093/sleep/zsad001
compute_sri(), compute_cosinor()
## Not run: result <- run_pipeline_native("recordings/P001.txt", tz = "America/Sao_Paulo") compute_lri(result) compute_lri(result, threshold = 50) ## End(Not run)## Not run: result <- run_pipeline_native("recordings/P001.txt", tz = "America/Sao_Paulo") compute_lri(result) compute_lri(result, threshold = 50) ## End(Not run)
Computes the standard non-parametric circadian rhythm analysis variables
from an actigraphy recording. IS and IV are computed from the
hourly-mean activity profile (p = 24), matching pyActigraphy's actual
_interdaily_stability()/_intradaily_variability() (not the
population-variance formula in Gonçalves et al. 2014 or Van Someren et
al. 1999's own text – the real implementation uses sample variance,
ddof = 1; see Details). L5/M10 and their onsets
follow a different convention – see below – matching the notebook this
package's Vallim-pipeline comparisons were validated against.
compute_npcra( x, epoch_s = NULL, L5_hours = 5, M10_hours = 10, window_days = NULL, trim_to_d1 = TRUE, exclude_offwrist = FALSE )compute_npcra( x, epoch_s = NULL, L5_hours = 5, M10_hours = 10, window_days = NULL, trim_to_d1 = TRUE, exclude_offwrist = FALSE )
x |
A |
epoch_s |
|
L5_hours |
|
M10_hours |
|
window_days |
|
trim_to_d1 |
|
exclude_offwrist |
|
The following variables are computed:
ISInterdaily stability — consistency of the 24 h rest-activity pattern across days (range 0–1; higher = more stable). From the hourly-mean profile (p = 24).
IVIntradaily variability — fragmentation of the rest-activity rhythm (>= 0; higher = more fragmented). From the hourly-mean profile (p = 24).
ISmMean of IS computed at every divisor of 1440 minutes
between 1 and 60 min (22 resolutions total: 1, 2, 3, 4, 5, 6, 8, 9,
10, 12, 15, 16, 18, 20, 24, 30, 32, 36, 40, 45, 48, 60), matching
Cell 16's _ISm_IVm_FREQS loop. Unlike IS, missing bins at each
resolution are omitted (not zero-filled).
IVmAs ISm, for IV.
RARelative amplitude — contrast between the most active 10 h window (M10) and least active 5 h window (L5) (range 0–1).
L5Mean activity during the least active 5-hour window, found by a rolling mean over a 10-min-resampled series, searched globally across the whole recording (not the p = 24 hourly profile used for IS/IV).
L5_onsetWall-clock time ("HH:MM") of the end of the least-active window – the time of day, wrapped at 24 h regardless of which calendar day the window actually falls on.
M10As L5, for the most active 10-hour window.
M10_onsetAs L5_onset, for the most-active window.
IS/IV build a 1h-resampled series X first: missing hourly bins get
a real zero (matching Python's s_1h = s.resample('1h').mean().fillna(0)),
not silent omission. X is grouped by hour-of-day into the p = 24 hourly
profile Xh. Both variables then use sample variance (divide by
n - 1, matching pandas' .var() default) rather than the population
variance (divide by n) that the classic Witting/Van Someren/Gonçalves
formulas describe on paper:
with N the number of hourly bins in the (zero-filled) recording and p
the number of hour-of-day groups present (24 for any recording spanning a
full day). The two formulas share the same denominator, matching
pyActigraphy's d_1h = data.var() being computed once and reused for both.
ISm/IVm repeat this same computation at 22 other bin widths (every
divisor of 1440 min between 1-60 min) and average the results – but
missing bins at those resolutions are omitted from N (matching
.dropna()), not zero-filled like the main IS/IV. A resolution is
only excluded from the average if it has 1 or fewer bins; if the
IS/IV formula itself degenerates (e.g. zero variance) at some other
resolution, that NaN/Inf is included in the average like any other
value, exactly matching the real Python (a try/except around the whole
per-frequency block, not a check on the computed value).
A tibble with columns participant_id, window_start (if
window_days is set), IS, IV, ISm, IVm, RA, L5, L5_onset,
M10, M10_onset, n_days, n_epochs.
Gonçalves, B. S. B., Adamowicz, T., Louzada, F. M., Moreno, C. R., & Araujo, J. F. (2014). A fresh look at the use of nonparametric analysis in actimetry. Sleep Medicine Reviews, 20, 84–91. doi:10.1016/j.smrv.2014.06.002
Van Someren, E. J. W., Swaab, D. F., Colenda, C. C., Cohen, W., McCall, W. V., & Rosenquist, P. B. (1999). Bright light therapy: Improved sensitivity to its effects on rest-activity rhythms in Alzheimer patients by application of nonparametric methods. Chronobiology International, 16(4), 505–518. doi:10.3109/07420529908998724
## Not run: rec <- read_actigraphy("recordings/P001.txt") # Single estimate over the full recording compute_npcra(rec) # Per-fortnight estimates compute_npcra(rec, window_days = 14) ## End(Not run)## Not run: rec <- read_actigraphy("recordings/P001.txt") # Single estimate over the full recording compute_npcra(rec) # Per-fortnight estimates compute_npcra(rec, window_days = 14) ## End(Not run)
Calculates mean sleep metrics separately for all nights, weekday nights,
and weekend/holiday nights. Output column names and derived metrics mirror
compute_sleep_metrics() from Julia Ribeiro da Silva Vallim's
pipeline_functions_fix27.py.
compute_sleep_metrics(x, ...) ## S3 method for class 'zeitr_result' compute_sleep_metrics( x, min_tib_h = 5, tz = "UTC", holidays = x$holidays, free_days = x$free_days, ... ) ## Default S3 method: compute_sleep_metrics( x, min_tib_h = 5, tz = "UTC", holidays = NULL, free_days = c("Saturday", "Sunday"), ... )compute_sleep_metrics(x, ...) ## S3 method for class 'zeitr_result' compute_sleep_metrics( x, min_tib_h = 5, tz = "UTC", holidays = x$holidays, free_days = x$free_days, ... ) ## Default S3 method: compute_sleep_metrics( x, min_tib_h = 5, tz = "UTC", holidays = NULL, free_days = c("Saturday", "Sunday"), ... )
x |
A |
... |
Not used; reserved for forward compatibility with future methods. |
min_tib_h |
|
tz |
|
holidays |
Holidays to treat as free days in addition to the days in
|
free_days |
A character vector of day names ( |
Dispatches on the class of x:
data.frame / tibble – treated as a nights table (same behaviour
as the original function signature).
zeitr_result – extracts x$nights and inherits x$holidays and
x$free_days automatically.
A night is assigned to the free-day group when the get-up date falls on
one of the days in free_days or matches an entry in holidays. Day-of-week
is determined via the ISO 8601 weekday number (format(date, "%u")) rather
than weekdays(), which is locale-dependent. All remaining nights are
workday nights.
Both sleep_onset_h and sleep_offset_h use a circular mean, treating
clock hours as angles on a 24-hour circle so values near midnight from
opposite sides average correctly (e.g. 23:30 and 00:30 average to
00:00, not 12:00).
fps_h (free period sleep) equals fpr_tib_h - (latencia_min + inertia_min) / 60 – TBT net of sleep onset latency and sleep inertia.
dp_midsleep_min is the circular SD (R-bar mean-resultant-length
formula) of per-night mid-sleep, in minutes – matching production
Python's _std_circular_h() exactly, not a plain SD. dp_tst_min is a
plain SD of per-night TST, in minutes.
A named list with metrics for three groups (overall, wd =
workday, fd = free day):
n_overall, n_wd, n_fd
Night counts.
sleep_onset_h, sleep_offset_h
Circular mean onset and offset in decimal hours.
fpr_tib_hMean TBT in hours.
fps_hMean free period sleep (TBT - SOL - SOI) in hours.
tst_hMean TST in hours.
latencia_min, inertia_min
Mean SOL and SOI in minutes.
waso_minMean WASO in minutes.
sleep_eff_pctMean sleep efficiency in percent (0-100).
tst_24h_hSame as tst_h (24-h TST for main sleep only).
dp_midsleep_minCircular SD of mid-sleep, in minutes.
dp_tst_minPlain SD of TST, in minutes.
Workday and free-day metrics carry the suffix _wd and _fd.
compute_cpd_metrics(), run_pipeline_native()
## Not run: # From a zeitr_result: holidays and free_days forwarded automatically result <- run_pipeline_native("recordings/P001.txt", tz = "America/Sao_Paulo", holidays = my_holidays, free_days = c("Saturday", "Sunday")) sm <- compute_sleep_metrics(result, tz = "America/Sao_Paulo") # Non-standard schedule: Friday + Saturday as free days sm <- compute_sleep_metrics(result$nights, tz = "America/Sao_Paulo", holidays = my_holidays, free_days = c("Friday", "Saturday")) sm$tst_h # mean TST in hours sm$sleep_onset_h # mean sleep onset (circular, decimal hours) sm$dp_midsleep_min # within-person SD of mid-sleep in minutes ## End(Not run)## Not run: # From a zeitr_result: holidays and free_days forwarded automatically result <- run_pipeline_native("recordings/P001.txt", tz = "America/Sao_Paulo", holidays = my_holidays, free_days = c("Saturday", "Sunday")) sm <- compute_sleep_metrics(result, tz = "America/Sao_Paulo") # Non-standard schedule: Friday + Saturday as free days sm <- compute_sleep_metrics(result$nights, tz = "America/Sao_Paulo", holidays = my_holidays, free_days = c("Friday", "Saturday")) sm$tst_h # mean TST in hours sm$sleep_onset_h # mean sleep onset (circular, decimal hours) sm$dp_midsleep_min # within-person SD of mid-sleep in minutes ## End(Not run)
Computes the Sleep Regularity Index (Phillips et al. 2017): a measure of day-to-day consistency in the sleep/wake pattern, based on the probability that an epoch's sleep/wake state matches the state at the same clock time exactly 24 h later (or earlier), averaged across the whole recording. Ranges from -100 (perfectly inverted day-to-day) to +100 (perfectly regular); 0 corresponds to chance-level agreement.
compute_sri(x, epoch_s = NULL, max_gap_min = 30, algo = "vallim")compute_sri(x, epoch_s = NULL, max_gap_min = 30, algo = "vallim")
x |
A |
epoch_s |
|
max_gap_min |
|
algo |
|
algo = "vallim" (default) ports Fix 30 of the Python reference
pipeline (SRI_vallim): sleep/wake comes directly from the epoch-level
state column already produced by zeitR's own pipelines
(run_pipeline() / run_pipeline_native()) – the same classification
compute_sleep_metrics() and compute_cpd_metrics() already treat as
ground truth for this recording. Off-wrist handling mirrors Fix 30
exactly: off-wrist epochs (state == 4) are treated as missing. Gaps of
max_gap_min minutes or less are interpolated (forward-filled from the
last valid epoch, or back-filled from the next valid epoch when the gap
starts at the very beginning of the recording); longer gaps are left as
missing and excluded from the day-to-day comparison entirely, rather than
being counted as a non-match. The aggregation is a single flat average
over every valid 24h-apart epoch pair, pooled across the whole recording:
where if the sleep/wake state at epoch
matches the state 24 h later, otherwise, and is the number
of epoch pairs where both epochs have a valid (non-missing) state. This
matches Julia's own compute_sri_vallim() replica (also a flat pooled
ratio, not a per-time-of-day average – see below).
algo = "sadeh" instead derives sleep/wake from the raw activity
signal via the Sadeh et al. (1994) algorithm, matching pyActigraphy's
actual Sadeh()/SleepRegularityIndex() (Cell 16 of
vs_condor_py_pipeline_fix30_jrsv.ipynb) exactly – both sourced from
pyActigraphy's real code (pyActigraphy/sleep/scoring_base.py,
_sadeh(); pyActigraphy/sleep/scoring/sri.py, sri()), not
reconstructed from documentation. Two precise details that differ from
the "vallim" path above:
Aggregation is a two-step average, not a flat pooled one.
pyActigraphy's sri() first groups epochs by time-of-day (hour,
minute, second) across all days, averages day-to-day stability
within each time-of-day slot, and only then averages across
slots. This is mathematically different from a flat pooled average
whenever slots have unequal numbers of valid day-pairs (e.g. a
partial first/last day) – both "vallim" and "sadeh" are ported
faithfully to their own respective real source, not reconciled to
use the same aggregation.
No off-wrist handling. Sadeh scores whatever raw activity it
is given; there is no off-wrist masking/interpolation step in
pyActigraphy's own code for this path. max_gap_min has no effect
when algo = "sadeh".
Sadeh's own edge behaviour is also reproduced exactly: mean_W5/NAT
need a full centered 11-epoch window (5 before, self, 5 after) and are
NA for the first/last 5 epochs; sd_Last6 needs a full trailing
6-epoch window (self + 5 before) and is NA for the first 5 epochs;
logAct uses the following epoch's activity (shift(-1)) and is NA
for the last epoch. Where the resulting PS score is NA, pandas'
NaN > threshold evaluates to False (not propagated as missing), so
that epoch is scored wake (0), not excluded – reproduced here
explicitly, since R's NA > threshold gives NA, not FALSE.
algo = "ck" derives sleep/wake via pyActigraphy's native Cole-Kripke
implementation (.CK(), default settings = "30sec_max_non_overlap")
– a DIFFERENT weight set from the Condor-native ColeKripke class
used elsewhere in zeitR's pipeline (R/cole_kripke.R); the two share an
algorithm family name but are otherwise unrelated. Shares "sadeh"'s
two-step SRI aggregation and lack of off-wrist handling (see above).
Despite the reference notebook resampling to 30-second bins before
calling this, that round-trip is a mathematical no-op on data that was
only ever 1-minute resolution (verified by direct execution – see
?.ck_native_score), so this operates directly on native-resolution
activity with no resampling needed. Also applies Webster's (1982)
rescoring rules afterward, matching pyActigraphy's default. Uses the
opposite threshold polarity from Sadeh (D < threshold = sleep here,
vs PS > threshold = sleep for Sadeh) – both faithful to their own
respective source.
algo = "scripps" derives sleep/wake via pyActigraphy's native Scripps
Clinic implementation (Scripps()/_scripps()). Structurally identical
to algo = "ck" – same centered rolling weighted dot product, same
D < threshold = sleep polarity, same two-step SRI aggregation and
lack of off-wrist handling – just a different scale/window/threshold,
and no Webster rescoring step (pyActigraphy's Scripps() doesn't call
rescore(), unlike CK()).
algo = "roenneberg" derives sleep/wake via pyActigraphy's native
Roenneberg et al. algorithm (roenneberg()) – by far the most
involved of the four: trend extraction (a 24h centered rolling mean,
allowing a partial window down to 12h at the recording's edges), a
15%-of-trend threshold categorization, seed-finding (candidate
sleep-onset runs at least 30 min long), then an iterative
correlation-based bout-cleaning loop (each candidate onset is tested
against a family of triangular "sleep bout ending at position i"
templates over the following 12h, accepting the HIGHEST correlation
peak as the bout's offset – not simply the first qualifying one; see
?.find_highest_peak_idx for a real version mismatch this caught).
Shares the two-step SRI aggregation and lack of off-wrist handling
with the other three raw-activity algorithms above. No rescoring step
(rescoring is specific to CK()).
A tibble with columns participant_id, sri, n_pairs (number
of valid 24h-apart epoch comparisons used; NA for algo != "vallim",
whose aggregation isn't a single pooled pair count), and
n_epochs. sri is NA if the recording is shorter than 24 h, or
(for algo = "vallim") if no valid pairs remain after off-wrist
exclusion.
Phillips, A. J. K., Clerx, W. M., O'Brien, C. S., Sano, A., Barger, L. K., Picard, R. W., Lockley, S. W., Klerman, E. B., & Czeisler, C. A. (2017). Irregular sleep/wake patterns are associated with poorer academic performance and delayed circadian and sleep/wake timing. Scientific Reports, 7, 3216. doi:10.1038/s41598-017-03171-4
Sadeh, A., Sharkey, M., & Carskadon, M. A. (1994). Activity-Based Sleep-Wake Identification: An Empirical Test of Methodological Issues. Sleep, 17(3), 201-207. doi:10.1093/sleep/17.3.201
compute_npcra(), compute_sleep_metrics(),
run_pipeline_native()
## Not run: result <- run_pipeline_native("recordings/P001.txt", tz = "America/Sao_Paulo") compute_sri(result) compute_sri(result, algo = "sadeh") ## End(Not run)## Not run: result <- run_pipeline_native("recordings/P001.txt", tz = "America/Sao_Paulo") compute_sri(result) compute_sri(result, algo = "sadeh") ## End(Not run)
Scores each epoch within detected sleep periods as wake or sleep using
score_epochs_cole_kripke(), then computes per-night statistics. This is a
faithful port of the Python condor_pipeline detect_waso: night
boundaries are built by .nights_df() (the search_gap = FALSE path of the
reference nights_df), each night's ZCM is scored with Cole-Kripke, and the
epoch-level state is rebuilt from a zero (wake) base so that within-night
WASO-wake epochs and all epochs outside detected nights are scored as wake.
compute_waso(x, wake_thresh = 60L, search_gap = FALSE)compute_waso(x, wake_thresh = 60L, search_gap = FALSE)
x |
A tibble as returned by |
wake_thresh |
|
search_gap |
|
The following metrics are computed for each detected night / nap:
| Metric | Definition |
tbt |
Total Bed Time — epochs from bed time to get-up time |
tst |
Total Sleep Time — tbt - waso - sol - soi |
waso |
Wake After Sleep Onset — wake epochs between sleep onset and final wake |
sol |
Sleep Onset Latency — epochs from bed time to first sleep epoch |
soi |
Sleep Offset Inertia — trailing wake epochs at end of sleep period |
nw |
Number of awakenings — count of wake-onset transitions |
eff |
Sleep efficiency — tst / tbt
|
A list with two elements:
nightsA tibble with one row per detected night/nap and
columns night, is_nap, bed_time, get_up_time, tbt, tst,
waso, sol, soi, nw, eff.
dataThe input tibble x with state and sleep updated
with epoch-level Cole-Kripke wake/sleep scores.
Cole, R. J., Kripke, D. F., Gruen, W., Mullaney, D. J., & Gillin, J. C. (1992). Automatic sleep/wake identification from wrist activity. Sleep, 15(5), 461–469. doi:10.1093/sleep/15.5.461
## Not run: rec <- read_acttrust("recordings/P001.txt") prep <- prepare_actigraphy(rec) prep <- detect_offwrist_bimodal(prep) prep <- detect_sleep_crespo(prep) prep <- detect_naps_crespo(prep) result <- compute_waso(prep) result$nights ## End(Not run)## Not run: rec <- read_acttrust("recordings/P001.txt") prep <- prepare_actigraphy(rec) prep <- detect_offwrist_bimodal(prep) prep <- detect_sleep_crespo(prep) prep <- detect_naps_crespo(prep) result <- compute_waso(prep) result$nights ## End(Not run)
Standalone sleep-bout detector for raw actigraphy, independent of zeitR's
main Crespo-based pipeline (detect_sleep_crespo()) or the Vallim native
pipeline (run_pipeline_native()). Intended for LIDS analysis on activity
recordings that haven't been run through either of those. If you already
have a zeitr_result, pass it straight to compute_lids() instead
(bout_source = "state", the default there) and skip this function.
detect_lids_bouts( datetime, activity, relative_threshold = 0.15, ma_window_h = 24, bridge_min = 5, min_bout_min = 30, max_gap_min = 15, main_window = c("18:00", "08:00"), duration_range = c(3, 12), one_per_night = TRUE )detect_lids_bouts( datetime, activity, relative_threshold = 0.15, ma_window_h = 24, bridge_min = 5, min_bout_min = 30, max_gap_min = 15, main_window = c("18:00", "08:00"), duration_range = c(3, 12), one_per_night = TRUE )
datetime |
|
activity |
Numeric vector of activity counts, same length as
|
relative_threshold |
|
ma_window_h |
|
bridge_min |
|
min_bout_min |
|
max_gap_min |
|
main_window |
|
duration_range |
|
one_per_night |
|
Ported (with one bug fixed – see below) from a prototype R notebook translation of Mario Leocadio-Miguel's method, itself adapted from the relative-immobility algorithm used in Winnebeck et al. (2018) and Hammad et al. (2026):
Compute a ma_window_h-centered moving average of activity (the
recording's own baseline).
Flag epochs where activity < relative_threshold times that
moving average as candidate sleep.
Consolidate: brief active blips (<= bridge_min minutes),
surrounded on both sides by candidate-sleep epochs, are relabelled as
sleep.
Keep only consolidated runs lasting >= min_bout_min.
Fuse bouts separated by a gap <= max_gap_min. (This is the
step truncated mid-statement in the source notebook,
fused.a... – reimplemented in full here as the internal
.fuse_bouts(). Unlike the notebook, which pads fused gaps with
NaN to preserve a regular time axis, the fused bout here simply
spans start to end
over the original activity values – fine for bout timing, but if
you need the gap epochs excluded from the LIDS fit itself, mask them
to NA in activity beforehand.)
Restrict to bouts starting within main_window (default
18:00-08:00), excluding daytime naps / recording artefacts.
Filter by total duration (duration_range) and, if
one_per_night = TRUE, keep only the longest bout per calendar night.
The moving-average step uses zeitR's border-replicated
rolling_mean_cpp() rather than pandas' min_periods=1 edge behaviour
(which shrinks the window near the recording boundary instead of
replicating the edge value) – a minor difference confined to the first/
last ma_window_h/2 hours of the recording.
A tibble with one row per detected bout: bout_id, bout_start,
bout_end, duration_h, n_epochs.
Winnebeck, E. C., Fischer, D., Leise, T., & Roenneberg, T. (2018). Dynamics and Ultradian Structure of Human Sleep in Real Life. Current Biology, 28(1), 49-59.e5. doi:10.1016/j.cub.2017.11.063
compute_lids(), lids_transform(), fit_lids()
Faithful port of the Python nap_wrapper: runs the full CSPD model in nap
mode (detect_naps = TRUE) on the currently-awake epochs (state == 0) and
merges the detected naps into the sleep state. Must be run after
detect_sleep_crespo().
detect_naps_crespo(x, epoch_h = NULL, params = .cspd_nap_params())detect_naps_crespo(x, epoch_h = NULL, params = .cspd_nap_params())
x |
A tibble as returned by |
epoch_h |
|
params |
CSPD nap configuration list (default |
Nap detection uses a nap-mode MSP (a high zero-proportion combined with a
low adaptive-median activity, .crespo_nap_msp()) followed by the same
bed-time / get-up-time refiners as the main sleep detection, with the nap
parameter set (.cspd_nap_params()) and nap-specific minimum-length
post-processing. Detected naps are written as state == 1 (merged into
"sleep"), matching nap_wrapper, which assigns
state[wake] = 1 - refined_output (i.e. naps are not a distinct state).
The input tibble x with state and sleep columns updated. Nap
epochs become state == 1 and sleep == 1; off-wrist (state == 4) and
existing main-sleep epochs are preserved.
Crespo, C., Aboy, M., Fernández, J. R., & Mojón, A. (2012). Automatic identification of activity-rest periods based on actigraphy. Journal of Medical and Biological Engineering, 32(4), 249–256. doi:10.5405/jmbe.1033
detect_sleep_crespo() for main sleep period detection.
## Not run: rec <- read_acttrust("recordings/P001.txt") prep <- prepare_actigraphy(rec) prep <- detect_offwrist_bimodal(prep) prep <- detect_sleep_crespo(prep) prep <- detect_naps_crespo(prep) ## End(Not run)## Not run: rec <- read_acttrust("recordings/P001.txt") prep <- prepare_actigraphy(rec) prep <- detect_offwrist_bimodal(prep) prep <- detect_sleep_crespo(prep) prep <- detect_naps_crespo(prep) ## End(Not run)
Detects periods where the actigraph was not worn using the bimodal algorithm developed by Condor Instruments. The algorithm proceeds in three stages:
detect_offwrist_bimodal( x, hws = 10L, activity_quantile = 0.15, min_norm_activity = 0.015, nbins = 100L, min_offwrist_length = 10L, min_temp_threshold = 0.35 )detect_offwrist_bimodal( x, hws = 10L, activity_quantile = 0.15, min_norm_activity = 0.015, nbins = 100L, min_offwrist_length = 10L, min_temp_threshold = 0.35 )
x |
A tibble as returned by |
hws |
|
activity_quantile |
|
min_norm_activity |
|
nbins |
|
min_offwrist_length |
|
min_temp_threshold |
|
Feature extraction — rolling median of PIM activity and rolling
signal/variance of internal temperature are computed over a symmetric
window of half-width hws epochs.
Bimodal temperature threshold — among epochs with low activity
median (below the activity_quantile quantile of normalised activity),
a 2-component Gaussian Mixture Model is fitted to the normalised
temperature distribution. The threshold between the two components is
taken as the minimum of the GMM density between the two means (or the
minimum of the smoothed histogram if that is lower). Ashman's D is
computed as a bimodality quality metric.
Initial classification & refinement — epochs simultaneously
exhibiting low activity median AND low temperature are marked as
off-wrist. Short spurious off-wrist runs shorter than
min_offwrist_length epochs are removed.
Off-wrist epochs are encoded as state == 4 (matching the Python
pipeline convention). The offwrist column is set to 0.25 for
off-wrist epochs for actogram overlay plotting.
The input tibble x with state and offwrist columns updated.
Off-wrist epochs have state == 4 and offwrist == 0.25.
The bimodal off-wrist algorithm was developed by Julius A. P. P. de Paula at Condor Instruments (2023). It is not published in peer-reviewed literature but the source code is available in the circadiaBase pipeline repository. The Ashman D statistic is described in:
Ashman, K. M., Bird, C. M., & Zepf, S. E. (1994). Detecting bimodality in astronomical datasets. The Astronomical Journal, 108, 2348. doi:10.1086/117248
## Not run: rec <- read_acttrust("recordings/P001.txt") prep <- prepare_actigraphy(rec) prep <- detect_offwrist_bimodal(prep) sum(prep$state == 4) # number of off-wrist epochs ## End(Not run)## Not run: rec <- read_acttrust("recordings/P001.txt") prep <- prepare_actigraphy(rec) prep <- detect_offwrist_bimodal(prep) sum(prep$state == 4) # number of off-wrist epochs ## End(Not run)
Identifies the main sleep period(s) in an actigraphy recording using the algorithm described in Crespo et al. (2012). The method applies an adaptive median filter to the activity signal, mitigates spuriously long zero runs, and thresholds the result at a quantile of the filtered signal. Morphological closing and opening operations are then used to smooth the binary sleep/wake estimate.
detect_sleep_crespo( x, epoch_h = NULL, median_filter_h = 8, pad_h = 1, sleep_quantile = 0.365, morph_size = 61L, consec_zeros_thr = 15L, awake_zeros_thr = 2L, sleep_zeros_thr = 30L, zero_mitigation_q = 0.33, min_short_window_thr = 1, refine = TRUE, condition = 0L )detect_sleep_crespo( x, epoch_h = NULL, median_filter_h = 8, pad_h = 1, sleep_quantile = 0.365, morph_size = 61L, consec_zeros_thr = 15L, awake_zeros_thr = 2L, sleep_zeros_thr = 30L, zero_mitigation_q = 0.33, min_short_window_thr = 1, refine = TRUE, condition = 0L )
x |
A tibble as returned by |
epoch_h |
|
median_filter_h |
|
pad_h |
|
sleep_quantile |
|
morph_size |
|
consec_zeros_thr |
|
awake_zeros_thr |
|
sleep_zeros_thr |
|
zero_mitigation_q |
|
min_short_window_thr |
|
refine |
|
condition |
|
The input tibble x with state and sleep columns updated.
Sleep epochs have state == 1 and sleep == 1; off-wrist epochs
(state == 4) are preserved and excluded from the sleep column. With
refine = TRUE the sleep epochs delimit the refined sleep PERIODS (the
Python refined_output); per-epoch wake/sleep within them is scored later
by compute_waso().
Crespo, C., Aboy, M., Fernández, J. R., & Mojón, A. (2012). Automatic identification of activity-rest periods based on actigraphy. Journal of Medical and Biological Engineering, 32(4), 249–256. doi:10.5405/jmbe.1033
detect_naps_crespo() for secondary sleep period (nap) detection.
## Not run: rec <- read_acttrust("recordings/P001.txt") prep <- prepare_actigraphy(rec) prep <- detect_offwrist_bimodal(prep) prep <- detect_sleep_crespo(prep) ## End(Not run)## Not run: rec <- read_acttrust("recordings/P001.txt") prep <- prepare_actigraphy(rec) prep <- detect_offwrist_bimodal(prep) prep <- detect_sleep_crespo(prep) ## End(Not run)
Applies the published ActTrust(R)/GT3X+ regression equation
(sqrt(MET) = b0 + b1 * sqrt(activity_counts)) from
pa_equations() to convert raw activity counts into estimated metabolic
equivalents (METs).
estimate_ee( counts, device = c("ACTT", "GT3X+"), placement = c("hip", "wrist"), equation_set = "batista2026" )estimate_ee( counts, device = c("ACTT", "GT3X+"), placement = c("hip", "wrist"), equation_set = "batista2026" )
counts |
Numeric vector of activity counts (counts/min, over the same
epoch length used by the source equation set – 1-minute for
|
device |
|
placement |
|
equation_set |
|
Numeric vector of estimated METs, same length as counts.
pa_equations() for the underlying coefficients and their
limitations, classify_pa_intensity() to convert METs into intensity
bands.
estimate_ee(c(0, 5000, 25000, 50000), device = "ACTT", placement = "hip")estimate_ee(c(0, 5000, 25000, 50000), device = "ACTT", placement = "hip")
Converts the epoch-level data tibble from run_pipeline() or
run_pipeline_native() into the tidy hypnogram format expected by
hypnoR metric functions.
export_hypnogram( result, subject_id = NULL, source = "zeitR", drop_offwrist = FALSE, epoch_sec = NULL )export_hypnogram( result, subject_id = NULL, source = "zeitR", drop_offwrist = FALSE, epoch_sec = NULL )
result |
A |
subject_id |
|
source |
|
drop_offwrist |
|
epoch_sec |
|
The coarse (3-state) stage mapping used by zeitR is:
state |
ZCMn |
Stage |
0 |
any | "W" |
1, 7 |
> 0 |
"Sleep" |
1, 7 |
== 0 |
"Quiet sleep" |
4 |
any | "W"
|
Zero-count epochs within sleep (ZCMn == 0) are mapped to "Quiet sleep"
as the standard actigraphy proxy for quiet/deep sleep. When ZCMn is not
present in the data, all sleep epochs are mapped to "Sleep".
The stage column is an ordered factor with levels
c("W", "Sleep", "Quiet sleep"), matching hypnoR's coarse resolution
contract. "Quiet sleep" is not produced by actigraphy but the level is
present so downstream hypnoR functions do not throw factor-level errors.
subject_id is taken from result$subject_id when not explicitly
supplied. Both run_pipeline() and run_pipeline_native() derive this
from the input filename stem automatically, so in most cases no manual
override is needed.
A tibble with columns:
epochInteger epoch index, 1-based.
timePOSIXct timestamp for the start of each epoch.
stageOrdered factor: c("W", "Sleep", "Quiet sleep").
subject_idCharacter subject identifier (omitted when not available).
sourceCharacter scorer label.
run_pipeline(), run_pipeline_native(), label_states()
## Not run: result <- run_pipeline("recordings/P001.txt", tz = "America/Sao_Paulo") # subject_id inferred from filename automatically hyp <- export_hypnogram(result) hyp$subject_id # "P001" # Override when filename does not match study code hyp <- export_hypnogram(result, subject_id = "STUDY_001") # Batch: subject_id inferred for every file automatically results <- run_pipeline_batch("recordings/", tz = "America/Sao_Paulo") hyps <- lapply(results, export_hypnogram) # Drop off-wrist epochs hyp_clean <- export_hypnogram(result, drop_offwrist = TRUE) ## End(Not run)## Not run: result <- run_pipeline("recordings/P001.txt", tz = "America/Sao_Paulo") # subject_id inferred from filename automatically hyp <- export_hypnogram(result) hyp$subject_id # "P001" # Override when filename does not match study code hyp <- export_hypnogram(result, subject_id = "STUDY_001") # Batch: subject_id inferred for every file automatically results <- run_pipeline_batch("recordings/", tz = "America/Sao_Paulo") hyps <- lapply(results, export_hypnogram) # Drop off-wrist epochs hyp_clean <- export_hypnogram(result, drop_offwrist = TRUE) ## End(Not run)
Converts the epoch-level state vector produced by detect_sleep_crespo()
into a per-episode tibble analogous to the Python SleepPipeline's
results.nights. Contiguous on-wrist sleep periods are delimited by
.nights_df(); per-epoch wake/sleep within each period is scored by
score_epochs_cole_kripke() to derive WASO, SOL, SOI, TST, EFF, and NW.
All time metrics are returned in minutes.
extract_sleep_episodes(data, wake_thresh = 60L)extract_sleep_episodes(data, wake_thresh = 60L)
data |
A tibble as returned by |
wake_thresh |
|
A tibble with one row per detected episode and columns bts
(POSIXct), gts (POSIXct), tbt, tst, sol, soi, waso
(minutes), nw (integer), eff (0-1), nap (logical, always FALSE
– classification into main/secondary happens in
classify_sleep_episodes()).
classify_sleep_episodes(), run_pipeline_native()
For a fixed period , the sloped-cosine model
is linear in , so it is solved by ordinary least
squares rather than a non-linear optimiser (Hammad et al. 2026). Candidate
periods are scanned over period_range in steps of period_step, and for
each one the fit's Munich Rhythmicity Index
(, Winnebeck et al.
2018) is computed. The period with the highest MRI is returned as the
bout's estimated ultradian cycle length – the same selection rule used by
pyActigraphy.analysis.LIDS.lids_fit(), generalised here to include the
linear slope term.
fit_lids(lids, epoch_min = 1, period_range = c(30, 180), period_step = 2)fit_lids(lids, epoch_min = 1, period_range = c(30, 180), period_step = 2)
lids |
Numeric vector of (smoothed) LIDS values for one sleep bout,
as returned by |
epoch_min |
|
period_range |
|
period_step |
|
A named list:
period_minEstimated cycle length (minutes) at peak MRI.
amplitude.
phase_rad, radians; 0 =
LIDS peak at bout start.
offsetInactivity level at bout start ().
slope_per_60minLinear trend, rescaled to LIDS units per hour.
pearson_rCorrelation between fitted and observed LIDS.
p_valueTwo-sided p-value for pearson_r (stats::cor.test()).
mriMunich Rhythmicity Index at the selected period.
Winnebeck, E. C., Fischer, D., Leise, T., & Roenneberg, T. (2018). Dynamics and Ultradian Structure of Human Sleep in Real Life. Current Biology, 28(1), 49-59.e5. doi:10.1016/j.cub.2017.11.063
Hammad, G., Schoch, S. F., Engelmann, M., Spock, Z., Kurth, S., & Winnebeck, E. C. (2026). Charting infant sleep cycle development using actigraphy. SLEEP.
lids_transform(), compute_lids()
set.seed(1) t <- seq(0, 300, by = 1) lids <- 85 + 15 * cos(2 * pi * t / 60) - 0.05 * t + rnorm(length(t), sd = 2) fit_lids(lids)set.seed(1) t <- seq(0, 300, by = 1) lids <- 85 + 15 * cos(2 * pi * t / 60) - 0.05 * t + rnorm(length(t), sd = 2) fit_lids(lids)
Converts the integer state column produced by the zeitR pipeline into a
human-readable factor. Useful for display, plotting, and export — the
internal state column always stays integer to preserve Python reference
parity.
label_states(x)label_states(x)
x |
integer (or numeric) vector of epoch states, as found in
|
| Integer | Label |
0 |
"wake" |
1 |
"sleep" |
4 |
"off-wrist" |
7 |
"nap"
|
Any value not in the table above is silently converted to NA.
An ordered factor with levels
c("wake", "sleep", "nap", "off-wrist"), the same length as x.
label_states(c(0L, 1L, 0L, 4L, 1L, 7L)) # [1] wake sleep wake off-wrist sleep nap # Levels: wake < sleep < nap < off-wrist ## Not run: result <- run_pipeline("recordings/P001.txt") result$data$state_label <- label_states(result$data$state) ## End(Not run)label_states(c(0L, 1L, 0L, 4L, 1L, 7L)) # [1] wake sleep wake off-wrist sleep nap # Levels: wake < sleep < nap < off-wrist ## Not run: result <- run_pipeline("recordings/P001.txt") result$data$state_label <- label_states(result$data$state) ## End(Not run)
Converts an activity signal into "inactivity" via the LIDS non-linear
transform, then smooths it – the first step of the LIDS ultradian-rhythm
pipeline (Winnebeck et al. 2018; Hammad et al. 2026). LIDS is only ever
computed inside an already-identified sleep bout; extract bouts first
via compute_lids() or detect_lids_bouts().
lids_transform( activity, epoch_min = 1, method = c("gaussian", "mva"), win_min = 30, sigma_min = 5 )lids_transform( activity, epoch_min = 1, method = c("gaussian", "mva"), win_min = 30, sigma_min = 5 )
activity |
Numeric vector of activity counts for a single sleep bout, in chronological order at a constant epoch length. |
epoch_min |
|
method |
|
win_min |
|
sigma_min |
|
where is the raw activity count at epoch . A LIDS value of
100 means zero movement; it falls toward 0 as movement increases.
Two smoothing methods are available, both over a nominal 30-min window:
"gaussian" (default) – Gaussian kernel, standard deviation
sigma_min (default 5 min), truncated at +/- 3 sigma (Hammad et al.
2026).
"mva" – centered moving average (Winnebeck et al. 2018;
pyActigraphy's default). Uses zeitR's border-replicated internal
rolling_mean_cpp(), which replicates the edge value rather than
shrinking the window near the bout boundary (pandas'
min_periods=1 behaviour) – a minor difference confined to the first/
last ~15 min of each bout.
Any NA in activity (e.g. a brief off-wrist gap inside an otherwise
valid bout) is linearly interpolated first – fit_lids() cannot handle
missing values.
Numeric vector of smoothed LIDS values, same length as activity.
Winnebeck, E. C., Fischer, D., Leise, T., & Roenneberg, T. (2018). Dynamics and Ultradian Structure of Human Sleep in Real Life. Current Biology, 28(1), 49-59.e5. doi:10.1016/j.cub.2017.11.063
Hammad, G., Schoch, S. F., Engelmann, M., Spock, Z., Kurth, S., & Winnebeck, E. C. (2026). Charting infant sleep cycle development using actigraphy: Longitudinal evidence for ultradian cycle lengthening within the first year of life. SLEEP.
fit_lids(), detect_lids_bouts(), compute_lids()
set.seed(1) activity <- pmax(0, 20 + 15 * sin(seq(0, 6 * pi, length.out = 360)) + rnorm(360, sd = 5)) lids <- lids_transform(activity, method = "gaussian")set.seed(1) activity <- pmax(0, 20 + 15 * sin(seq(0, 6 * pi, length.out = 360)) + rnorm(360, sd = 5)) lids <- lids_transform(activity, method = "gaussian")
Returns the regression coefficients and count-based cut-points for estimating energy expenditure (METs) and classifying physical activity (PA) intensity from ActTrust(R) (Condor Instruments) or ActiGraph(R) GT3X+ activity counts, hip or wrist placement.
pa_equations(equation_set = "batista2026")pa_equations(equation_set = "batista2026")
equation_set |
|
A tibble with one row per device x placement combination and columns:
device"ACTT" or "GT3X+".
placement"hip" or "wrist".
b0, b1
Intercept and slope of
sqrt(MET) = b0 + b1 * sqrt(activity_counts).
cut3, cut6, cut9
Published count/min cut-points
(95% CI midpoints) at the 3, 6, and 9 MET thresholds – i.e. the
count value at which the fitted equation crosses that MET level.
Provided for reference; classify_pa_intensity() classifies on
estimated METs directly rather than re-deriving these.
These equations come from a single controlled-laboratory validation study (Batista et al. 2026; N = 56 healthy adults aged 18-35; treadmill walking/running at 3-9 km/h only). Treat them as one available equation set, not a universal standard:
The paper's own Discussion section compares its GT3X+ (hip) cut-points
against two other published GT3X+ studies (Sasaki et al. 2011;
Santos-Lozano et al. 2013) and finds differences of 2-65% depending on
the MET threshold – and those two reference studies differ from each
other by 16-39%. The paper attributes that spread mainly to
sample-level characteristics rather than device or methodological
artefacts, but that reading applies to the comparison between Sasaki
and Santos-Lozano – it doesn't fully carry over to comparisons against
this paper's own equations, since all three studies used different
modelling approaches (Sasaki et al.: ActiGraph's two-regression model;
Santos-Lozano et al.: an artificial neural network; this paper: a
simple sqrt-transformed linear model with a device x placement
interaction). Cross-study cut-point differences therefore reflect model
choice as well as sample, not sample alone – and that's checkable to
different degrees: this paper's linear coefficients (b0/b1 below)
are transparent enough to compare term-by-term against Sasaki et al.'s
two-regression model, so a discrepancy there is at least diagnosable.
Santos-Lozano et al.'s cut-points come from an ANN, which has no
inspectable coefficients – there is no way to say why it disagrees
with the equations here, only that it does.
The ACTT (hip)/ACTT (wrist) equations are the first published cut-points for ActTrust(R) at all, so there is nothing yet to cross-check them against.
Validated only for laboratory treadmill walking/running in healthy young adults. Applying these equations to free-living data, other age groups (children, older adults), clinical populations, or other activity types is an extrapolation the source study explicitly flags as untested.
equation_set is exposed as an explicit argument (rather than hard-coding
a single table) so a future validation study covering a different
population or device can be added as an alternative set without changing
the estimate_ee() / classify_pa_intensity() API.
estimate_ee(), classify_pa_intensity()
pa_equations()pa_equations()
Renders epoch-level sleep/wake states as a raster with one row per calendar day and time-of-day (00:00 to 24:00) on the x-axis. The oldest day is at the top, following standard chronobiology convention.
plot_actogram( result, tz = NULL, title = NULL, colours = NULL, date_label_every = 7L, epoch_min = 1, base_size = 13 )plot_actogram( result, tz = NULL, title = NULL, colours = NULL, date_label_every = 7L, epoch_min = 1, base_size = 13 )
result |
A |
tz |
|
title |
|
colours |
Named character vector mapping state labels ( |
date_label_every |
|
epoch_min |
|
base_size |
|
A ggplot object.
plot_actogram_double(), plot_actogram_activity(),
actogram_colours(), label_states()
## Not run: result <- run_pipeline("recordings/P001.txt", tz = "America/Sao_Paulo") # Default colours plot_actogram(result) # Custom colours plot_actogram(result, colours = c(wake = "#E8D5B0", sleep = "#1C1A2E", nap = "#F0A500", "off-wrist" = "#C25E2A")) # Label every 14 days plot_actogram(result, date_label_every = 14L) ## End(Not run)## Not run: result <- run_pipeline("recordings/P001.txt", tz = "America/Sao_Paulo") # Default colours plot_actogram(result) # Custom colours plot_actogram(result, colours = c(wake = "#E8D5B0", sleep = "#1C1A2E", nap = "#F0A500", "off-wrist" = "#C25E2A")) # Label every 14 days plot_actogram(result, date_label_every = 14L) ## End(Not run)
Same double-plot layout as plot_actogram_double() — each day appears in
both the left and right column of adjacent rows — but each epoch is rendered
as a vertical bar whose height is proportional to the raw activity count
(default: ZCMn, the zero-crossing mean from ActTrust). Bars are coloured
by sleep/wake state, so the plot simultaneously conveys activity intensity
and state classification.
plot_actogram_activity( result, tz = NULL, title = NULL, colours = NULL, activity_col = "ZCMn", activity_cap_quantile = 0.99, log_scale = FALSE, date_label_every = 7L, epoch_min = 1, base_size = 13 )plot_actogram_activity( result, tz = NULL, title = NULL, colours = NULL, activity_col = "ZCMn", activity_cap_quantile = 0.99, log_scale = FALSE, date_label_every = 7L, epoch_min = 1, base_size = 13 )
result |
A |
tz |
|
title |
|
colours |
Named character vector mapping state labels ( |
activity_col |
|
activity_cap_quantile |
|
log_scale |
|
date_label_every |
|
epoch_min |
|
base_size |
|
Bar heights are capped at the activity_cap_quantile quantile of
non-zero epochs to prevent outlier activity bursts from compressing the
visible range for the rest of the recording. A thin baseline stub
(2 % of row height) is drawn for zero-activity epochs so that sleep periods
and off-wrist blocks remain faintly visible.
Actigraphy activity counts are typically right-skewed, with occasional
bursts far above the typical waking level. On the default linear scale
this compresses most of the meaningful variation among low-to-moderate
activity epochs into a thin sliver near the baseline. Set log_scale = TRUE to apply a log1p() transform (log(1 + x), so zero-activity
epochs map to 0 rather than -Inf) before capping and normalising –
this expands the low-activity range at the cost of visually compressing
the difference between already-high activity bursts.
A ggplot object.
plot_actogram(), plot_actogram_double(), actogram_colours()
## Not run: result <- run_pipeline("recordings/P001.txt", tz = "America/Sao_Paulo") plot_actogram_activity(result) # Cap at 95th percentile to highlight moderate activity plot_actogram_activity(result, activity_cap_quantile = 0.95) # Log scale: reveals structure among low-activity epochs that a linear # scale would otherwise compress near the baseline plot_actogram_activity(result, log_scale = TRUE) ## End(Not run)## Not run: result <- run_pipeline("recordings/P001.txt", tz = "America/Sao_Paulo") plot_actogram_activity(result) # Cap at 95th percentile to highlight moderate activity plot_actogram_activity(result, activity_cap_quantile = 0.95) # Log scale: reveals structure among low-activity epochs that a linear # scale would otherwise compress near the baseline plot_actogram_activity(result, log_scale = TRUE) ## End(Not run)
The classic chronobiology double-plot format. Each recording day is drawn twice: in the left column of its own row (x = 00:00 to 24:00) and in the right column of the row above (x = 24:00 to 48:00). This means consecutive pairs of days share a row, making circadian phase drift visible as a diagonal band across rows.
plot_actogram_double( result, tz = NULL, title = NULL, colours = NULL, date_label_every = 7L, epoch_min = 1, base_size = 13 )plot_actogram_double( result, tz = NULL, title = NULL, colours = NULL, date_label_every = 7L, epoch_min = 1, base_size = 13 )
result |
A |
tz |
|
title |
|
colours |
Named character vector mapping state labels ( |
date_label_every |
|
epoch_min |
|
base_size |
|
Row shows day on the left and day on the right.
Every day therefore appears twice in the plot (except the first, which has
no left-column predecessor, and the last, which has no right-column
successor). A dashed vertical line marks the 24 h boundary between the
two columns.
A ggplot object.
plot_actogram(), plot_actogram_activity(), actogram_colours()
## Not run: result <- run_pipeline("recordings/P001.txt", tz = "America/Sao_Paulo") plot_actogram_double(result) ## End(Not run)## Not run: result <- run_pipeline("recordings/P001.txt", tz = "America/Sao_Paulo") plot_actogram_double(result) ## End(Not run)
Transforms the output of read_acttrust() (or any reader that returns a
compatible tibble) into the working data frame expected by all detection
functions. Specifically:
prepare_actigraphy(x)prepare_actigraphy(x)
x |
A tibble as returned by |
Clamps int_temp and ext_temp to the physiological range [0, 42] °C.
Adds min-max scaled temperature columns int_temp_ and ext_temp_
(range [0, 1]) for plotting.
Ensures state, offwrist, and sleep columns are present and
initialised to 0.
The original tibble is never modified; a copy is returned.
A tibble of the same dimensions as x with additional or updated
columns: state, offwrist, sleep, int_temp_, ext_temp_.
## Not run: rec <- read_acttrust("recordings/P001.txt") prep <- prepare_actigraphy(rec) ## End(Not run)## Not run: rec <- read_acttrust("recordings/P001.txt") prep <- prepare_actigraphy(rec) ## End(Not run)
A device-agnostic wrapper that reads a raw actigraphy file and returns a
zeitr_recording object with $epochs (a tidy tibble) and $metadata
(a named list of device and recording information).
read_actigraphy(path, device = "acttrust", tz = "UTC", ...)read_actigraphy(path, device = "acttrust", tz = "UTC", ...)
path |
|
device |
|
tz |
|
... |
Additional arguments forwarded to the device-specific reader
(e.g. |
Currently supported devices:
"acttrust" — Condor Instruments ActTrust / ActTrust2 (.txt)
"axivity" — Axivity AX3/AX6 (.cwa), via read_axivity()
(requires the axR package; converts raw acceleration to epoch-level
counts with compute_activity_counts() – see read_axivity()'s
Details for validation caveats)
A zeitr_recording S3 object — a named list with:
$epochsA tibble with one row per epoch and columns
datetime, activity, int_temp, ext_temp, ZCMn,
state, offwrist, sleep.
$metadataA named list with subject, device_id,
device_model, firmware_version, interval_s, source_file,
and participant_id (derived from the filename stem).
read_actigraphy_dir() to read a whole directory at once.
## Not run: rec <- read_actigraphy("recordings/P001.txt") rec$epochs rec$metadata ## End(Not run)## Not run: rec <- read_actigraphy("recordings/P001.txt") rec$epochs rec$metadata ## End(Not run)
Applies read_actigraphy() to every file matching pattern in folder.
Returns a zeitr_study object — a named list of zeitr_recording objects,
one per file. Files that fail to parse are skipped with a warning.
read_actigraphy_dir( folder, device = "acttrust", pattern = "*.txt", tz = "UTC", ... )read_actigraphy_dir( folder, device = "acttrust", pattern = "*.txt", tz = "UTC", ... )
folder |
|
device |
|
pattern |
|
tz |
|
... |
Additional arguments forwarded to |
A zeitr_study S3 object — a named list of zeitr_recording
objects. Names are participant IDs (filename stems).
study_summary() to summarise a zeitr_study.
## Not run: study <- read_actigraphy_dir("recordings/", tz = "America/Sao_Paulo") study_summary(study) ## End(Not run)## Not run: study <- read_actigraphy_dir("recordings/", tz = "America/Sao_Paulo") study_summary(study) ## End(Not run)
Parses a Condor ActTrust .txt export into a tidy tibble. The file
format consists of a variable-length key-value header block followed by
semicolon-delimited epoch rows. The header ends at the line beginning with
DATE/TIME.
read_acttrust(path, tz = "UTC", encoding = "latin1")read_acttrust(path, tz = "UTC", encoding = "latin1")
path |
|
tz |
|
encoding |
|
A tibble with one row per epoch and the following columns:
datetimePOSIXct — epoch timestamp.
activitydouble — PIM activity count.
int_tempdouble — internal (on-body) temperature, degC.
ext_tempdouble — external (ambient) temperature, degC.
NA if unavailable.
ZCMndouble — normalised zero-crossing mode count.
NA if unavailable.
lightdouble — total light intensity (lux).
NA if unavailable.
statedouble — state column, initialised to 0.
offwristdouble — off-wrist indicator, initialised to 0.
sleepdouble — sleep indicator, initialised to 0.
The tibble carries a "zeitr_acttrust" class and a metadata attribute
(a named list with subject, device_id, device_model,
firmware_version, interval_s, source_file).
## Not run: rec <- read_acttrust("recordings/P001.txt") rec attr(rec, "metadata") ## End(Not run)## Not run: rec <- read_acttrust("recordings/P001.txt") rec attr(rec, "metadata") ## End(Not run)
Bridges axR::axivity_read_cwa()'s raw per-sample output (triaxial
acceleration at the device's native sampling rate) into the same
epoch-level, 9-column shape read_acttrust() produces – so an Axivity
recording can flow through the rest of zeitR (run_pipeline(),
compute_npcra(), compute_sri(), etc.) exactly like an ActTrust one.
read_axivity( path, tz = "UTC", epoch_sec = 60, filter_low = 0.25, filter_high = 2.5, zcm_threshold = 0.01, tat_threshold = 0.05 )read_axivity( path, tz = "UTC", epoch_sec = 60, filter_low = 0.25, filter_high = 2.5, zcm_threshold = 0.01, tat_threshold = 0.05 )
path |
|
tz |
|
epoch_sec |
|
filter_low, filter_high
|
|
zcm_threshold, tat_threshold
|
|
A tibble with one row per epoch and the same columns as
read_acttrust() (datetime, activity, int_temp, ext_temp,
ZCMn, light, state, offwrist, sleep), plus one extra column
not present there: TAT (time above threshold, seconds/epoch, from
compute_activity_counts()). activity is PIM. ext_temp is always
NA – Axivity devices have a single on-body temperature sensor, no
separate ambient sensor. The tibble carries a "zeitr_axivity" class
and a metadata attribute (a named list with device_id,
session_id, sample_rate, epoch_sec, filter_low, filter_high,
cwa_metadata (axR's raw device metadata string), source_file).
The raw-to-counts conversion itself is compute_activity_counts() –
already documented there as an unvalidated approximation of onboard
device processing (no reference converter exists to check it against).
Axivity devices additionally have no published or validated
filter/threshold preset at all (unlike ActTrust and GT3X+, which at
least have a documented processing description to approximate). The
filter_low/filter_high defaults here (0.25/2.5 Hz) are
compute_activity_counts()'s GT3X+-style preset, reused because it's
the closer starting point of the two existing options for a
research-grade wrist accelerometer like the AX3 – not because it has
been checked against real Axivity output. Treat activity/ZCMn from
this function as a rough approximation only; validate against a
reference (e.g. GGIR) before relying on it for any published analysis.
ZCMn is compute_activity_counts()'s raw ZCM count with no
additional normalisation applied – named ZCMn only for column-name
compatibility with read_acttrust()'s CK-scoring input, not because a
normalisation step has actually been performed.
axivity_read_cwa() reports sample_rate per sample (block-level, as
stored in the .cwa file). This function takes the single most common
value across the whole recording and uses it for the entire conversion;
if any samples report a different rate (a genuine rate change mid
recording, or a corrupt block), a warning names the discrepancy but the
dominant rate is still used throughout. Epoch boundaries and grouping for
light/int_temp averaging are derived from this same dominant rate,
matching compute_activity_counts()'s own epoch grouping exactly
(including which trailing samples get dropped).
axivity_read_cwa() tags timestamp as UTC by convention (the device's
own real-time clock, not a true UTC source) – exactly like
read_acttrust() does for ActTrust's DATE/TIME column. tz here
re-labels the same clock reading under the recording's actual local time
zone (via lubridate::force_tz(); no shift in wall-clock value), rather
than converting it – set it to the time zone the device's clock was
actually set to for correct circadian alignment downstream.
read_actigraphy() (device = "axivity"),
compute_activity_counts() for the underlying raw-to-counts
conversion and its validation caveats, read_acttrust() for the
column shape this matches.
## Not run: rec <- read_axivity("recordings/P001.cwa", tz = "America/Sao_Paulo") rec attr(rec, "metadata") ## End(Not run)## Not run: rec <- read_axivity("recordings/P001.cwa", tz = "America/Sao_Paulo") rec attr(rec, "metadata") ## End(Not run)
Orchestrates the complete pipeline for a single ActTrust recording:
run_pipeline( path, tz = "UTC", gap_s = 120, params = acttrust_params(), offwrist_args = list(), sleep_args = list(), nap_args = list(), quiet = FALSE )run_pipeline( path, tz = "UTC", gap_s = 120, params = acttrust_params(), offwrist_args = list(), sleep_args = list(), nap_args = list(), quiet = FALSE )
path |
|
tz |
|
gap_s |
|
params |
Device parameter preset, as returned by |
offwrist_args |
|
sleep_args |
|
nap_args |
|
quiet |
|
Read — read_acttrust()
Consistency check — check_consistency()
Prepare — prepare_actigraphy()
Off-wrist detection — detect_offwrist_bimodal()
Main sleep period detection — detect_sleep_crespo()
Nap detection — detect_naps_crespo()
WASO + nightly statistics — compute_waso()
A zeitr_result S3 object — a named list with:
subject_idcharacter — derived from the input filename stem.
source_filecharacter — absolute path to the input file.
datatibble — final epoch-level data frame with all state
columns populated.
nightstibble — per-night sleep statistics.
issuestibble — timestamp consistency issues (0 rows if none).
metadatalist — device and subject metadata from the file header.
run_pipeline_batch() for processing a directory of files.
## Not run: result <- run_pipeline("recordings/P001.txt", tz = "America/Sao_Paulo") result$nights result$data ## End(Not run)## Not run: result <- run_pipeline("recordings/P001.txt", tz = "America/Sao_Paulo") result$nights result$data ## End(Not run)
Applies run_pipeline() to every file matching pattern in folder,
returning a list of zeitr_result objects. Files that fail are skipped
with a warning.
run_pipeline_batch(folder, pattern = "*.txt", parallel = FALSE, ...)run_pipeline_batch(folder, pattern = "*.txt", parallel = FALSE, ...)
folder |
|
pattern |
|
parallel |
|
... |
Additional arguments forwarded to |
A named list of zeitr_result objects, one per successfully
processed file. Names are the file stem (subject IDs).
## Not run: results <- run_pipeline_batch("recordings/", tz = "America/Sao_Paulo") lapply(results, function(r) r$nights) # Parallel: process a large cohort across 4 workers future::plan(future::multisession(workers = 4)) results <- run_pipeline_batch("recordings/", tz = "America/Sao_Paulo", parallel = TRUE) ## End(Not run)## Not run: results <- run_pipeline_batch("recordings/", tz = "America/Sao_Paulo") lapply(results, function(r) r$nights) # Parallel: process a large cohort across 4 workers future::plan(future::multisession(workers = 4)) results <- run_pipeline_batch("recordings/", tz = "America/Sao_Paulo", parallel = TRUE) ## End(Not run)
Orchestrates the vendor-independent (Vallim) sleep analysis pipeline for a
single ActTrust recording. Shares the read / prepare / off-wrist / CSPD
epoch-scorer stages with run_pipeline(), then replaces Condor's
nights_df classification with the rule set developed by Julia Ribeiro da
Silva Vallim and ported from her condor pipeline (Fixes 25, 26a/b/c, 27, 29).
run_pipeline_native( path, tz = "UTC", gap_s = 120, params = acttrust_params(), offwrist_args = list(), sleep_args = list(), classify_args = list(), holidays = NULL, free_days = c("Saturday", "Sunday"), quiet = FALSE )run_pipeline_native( path, tz = "UTC", gap_s = 120, params = acttrust_params(), offwrist_args = list(), sleep_args = list(), classify_args = list(), holidays = NULL, free_days = c("Saturday", "Sunday"), quiet = FALSE )
path |
|
tz |
|
gap_s |
|
params |
Device parameter preset, as returned by |
offwrist_args |
|
sleep_args |
|
classify_args |
|
holidays |
Holidays to treat as free days in addition to the days in
|
free_days |
A character vector of day names ( |
quiet |
|
Steps:
Read – read_acttrust()
Consistency check – check_consistency()
Prepare – prepare_actigraphy()
Off-wrist detection – detect_offwrist_bimodal()
Epoch scoring – detect_sleep_crespo() (CSPD; same scorer as run_pipeline())
Episode extraction – extract_sleep_episodes()
Episode classification – classify_sleep_episodes() (JRSV rules)
Fine-grained state – compute_waso() (Cole-Kripke within periods, for result$data)
A zeitr_result S3 object with the same structure as run_pipeline(),
except nights additionally contains:
sleep_typecharacter – "main" or "secondary".
bed_time, get_up_time
POSIXct – bed_time is the first
sleep epoch, get_up_time is the first wake epoch (matches the
Python reference's bts/gts convention exactly).
The is_nap column is retained for backwards compatibility
(is_nap == (sleep_type == "secondary")).
run_pipeline() for the vendor (Condor) pipeline,
extract_sleep_episodes(), classify_sleep_episodes()
## Not run: result <- run_pipeline_native("recordings/P001.txt", tz = "America/Sao_Paulo") result$nights ## End(Not run)## Not run: result <- run_pipeline_native("recordings/P001.txt", tz = "America/Sao_Paulo") result$nights ## End(Not run)
Applies run_pipeline_native() to every file matching pattern in
folder, returning a named list of zeitr_result objects. Files that
fail are skipped with a warning.
run_pipeline_native_batch(folder, pattern = "*.txt", parallel = FALSE, ...)run_pipeline_native_batch(folder, pattern = "*.txt", parallel = FALSE, ...)
folder |
|
pattern |
|
parallel |
|
... |
Additional arguments forwarded to |
A named list of zeitr_result objects.
## Not run: results <- run_pipeline_native_batch("recordings/", tz = "America/Sao_Paulo") lapply(results, function(r) r$nights) # Parallel: process a large cohort across 4 workers future::plan(future::multisession(workers = 4)) results <- run_pipeline_native_batch("recordings/", tz = "America/Sao_Paulo", parallel = TRUE) ## End(Not run)## Not run: results <- run_pipeline_native_batch("recordings/", tz = "America/Sao_Paulo") lapply(results, function(r) r$nights) # Parallel: process a large cohort across 4 workers future::plan(future::multisession(workers = 4)) results <- run_pipeline_native_batch("recordings/", tz = "America/Sao_Paulo", parallel = TRUE) ## End(Not run)
Applies the Cole-Kripke algorithm to a vector of zero-crossing mode (ZCM)
activity counts, scoring each epoch as wake (1) or sleep (0) using a
weighted sum of activity in a surrounding window.
score_epochs_cole_kripke( zcm, P = 0.000464, weights_before = c(34.5, 133, 529, 375, 408, 400.5, 1074, 2048.5, 2424.5), weights_after = c(1920, 149.5, 257.5, 125, 111.5, 120, 69, 40.5) )score_epochs_cole_kripke( zcm, P = 0.000464, weights_before = c(34.5, 133, 529, 375, 408, 400.5, 1074, 2048.5, 2424.5), weights_after = c(1920, 149.5, 257.5, 125, 111.5, 120, 69, 40.5) )
zcm |
|
P |
|
weights_before |
|
weights_after |
|
Each epoch's score is computed as:
where is the ZCM count at epoch , and
are the before and after weight vectors from Cole et al. (1992),
and . Epochs with are scored as wake.
An integer vector the same length as zcm, with 1 indicating
wake and 0 indicating sleep.
Cole, R. J., Kripke, D. F., Gruen, W., Mullaney, D. J., & Gillin, J. C. (1992). Automatic sleep/wake identification from wrist activity. Sleep, 15(5), 461–469. doi:10.1093/sleep/15.5.461
## Not run: rec <- read_acttrust("recordings/P001.txt") scores <- score_epochs_cole_kripke(rec$ZCMn) table(scores) # 0 = sleep, 1 = wake ## End(Not run)## Not run: rec <- read_acttrust("recordings/P001.txt") scores <- score_epochs_cole_kripke(rec$ZCMn) table(scores) # 0 = sleep, 1 = wake ## End(Not run)
Computes compute_lids() for every participant in a batch of pipeline
results and summarises each participant's quality-filtered bouts into a
single row – the LIDS counterpart to study_sleep_metrics() and
study_summary(), making per-participant median cycle length, amplitude,
offset, and slope database-ready for tools like syncR::sync().
study_lids_metrics(results, min_bouts = 3, ...)study_lids_metrics(results, min_bouts = 3, ...)
results |
A named list of |
min_bouts |
|
... |
Forwarded to |
A tibble with one row per participant: participant_id,
n_bouts, n_bouts_passing, and (computed only over
passes_quality_filter == TRUE bouts, and only when there are at
least min_bouts of them) period_min_median, amplitude_median,
offset_median, slope_per_60min_median, plus an _iqr variant of
each.
compute_lids(), study_sleep_metrics()
## Not run: results <- run_pipeline_native_batch("recordings/", tz = "America/Sao_Paulo") study_lids_metrics(results) ## End(Not run)## Not run: results <- run_pipeline_native_batch("recordings/", tz = "America/Sao_Paulo") study_lids_metrics(results) ## End(Not run)
Computes compute_sleep_metrics() and compute_cpd_metrics() for every
participant in a batch of pipeline results and stacks them into a single
tibble with one row per participant – the sleep-timing/chronotype
counterpart to study_summary(), which covers NPCRA activity-rhythm
variables instead.
study_sleep_metrics( results, min_tib_h = 5, min_tib_eve_h = 3, tz = "UTC", holidays = NULL, free_days = NULL )study_sleep_metrics( results, min_tib_h = 5, min_tib_eve_h = 3, tz = "UTC", holidays = NULL, free_days = NULL )
results |
A named list of |
min_tib_h |
|
min_tib_eve_h |
|
tz |
|
holidays, free_days
|
Forwarded to both |
compute_sleep_metrics() and compute_cpd_metrics() each return a single
named list per participant with no participant identifier, and there was
previously no batch wrapper analogous to study_summary() for them. This
is that wrapper – intended to make these chronobiological phenotyping
metrics database-ready for tools like syncR::sync(), which expect one
row per participant with a shared participant_id column across sources.
If either metric computation fails for a participant (e.g. no free days
found, or no nights pass the min_tib_h filter), that participant's row
is filled with NA for the affected metrics and a warning is emitted –
the rest of the study is unaffected.
A tibble with one row per participant: participant_id, all
compute_sleep_metrics() columns (n_overall/n_wd/n_fd and the
twelve sleep-timing metrics with _wd/_fd suffixes), and all
compute_cpd_metrics() columns (n_nights_cpd, n_free_days,
n_workdays, msw_h/msf_h/msfsc_h and their _hms forms, sjl_h,
sjla_h, cpd_s/cpd_min/cpd_h).
study_summary() for the NPCRA (activity-rhythm) analogue,
compute_sleep_metrics(), compute_cpd_metrics(),
run_pipeline_native_batch()
## Not run: results <- run_pipeline_native_batch("recordings/", tz = "America/Sao_Paulo") study_sleep_metrics(results) # Feed straight into syncR::sync() sync(zeit = study_sleep_metrics(results)) ## End(Not run)## Not run: results <- run_pipeline_native_batch("recordings/", tz = "America/Sao_Paulo") study_sleep_metrics(results) # Feed straight into syncR::sync() sync(zeit = study_sleep_metrics(results)) ## End(Not run)
Computes per-participant summary statistics from a zeitr_study object
(as returned by read_actigraphy_dir()). For each recording, the function
computes NPCRA variables (IS, IV, RA, L5, M10) and basic recording quality
metrics.
study_summary(study, epoch_s = NULL, L5_hours = 5, M10_hours = 10)study_summary(study, epoch_s = NULL, L5_hours = 5, M10_hours = 10)
study |
A |
epoch_s |
|
L5_hours |
|
M10_hours |
|
A tibble with one row per participant and columns:
participant_idParticipant identifier (filename stem).
n_epochsTotal number of epochs in the recording.
n_daysRecording duration in days.
startPOSIXct — first epoch timestamp.
endPOSIXct — last epoch timestamp.
ISInterdaily stability.
IVIntradaily variability.
RARelative amplitude.
L5Mean activity in the least active 5 h window.
L5_onsetClock time of L5 midpoint.
M10Mean activity in the most active 10 h window.
M10_onsetClock time of M10 midpoint.
compute_npcra() for single-recording NPCRA, read_actigraphy_dir()
to create a zeitr_study, study_sleep_metrics() for the sleep-timing/
chronotype analogue (CPD, MSF/MSW, social jetlag).
## Not run: study <- read_actigraphy_dir("recordings/", tz = "America/Sao_Paulo") study_summary(study) ## End(Not run)## Not run: study <- read_actigraphy_dir("recordings/", tz = "America/Sao_Paulo") study_summary(study) ## End(Not run)