Actogram visualisation

An actogram is the standard visualisation for wrist actigraphy data. It lays out the full epoch-level state sequence as a raster – one row per calendar day, time of day on the x-axis – so that a recording spanning weeks can be read at a glance. zeitR provides three actogram variants:

Function Format Best for
plot_actogram() Single column, 24 h Overview, presentations
plot_actogram_double() Double-plotted, 48 h Phase drift, phase assessment
plot_actogram_activity() Double-plotted + activity bars Intensity + state simultaneously

All three accept either a zeitr_result list from the pipeline or a bare tibble with datetime and state columns.


Setup

The examples below use the ActTrust validation recording bundled with zeitR, run through run_pipeline_native().

FILE <- system.file("extdata", "input1.txt", package = "zeitR")
TZ   <- "America/Sao_Paulo"

result <- run_pipeline_native(FILE, tz = TZ, quiet = TRUE)
#> ℹ Reading 'input1.txt' ...
#> ✔ [input1] Done. 52 main night(s), 0 secondary episode(s).

Single-column actogram

plot_actogram() draws one row per calendar day with time of day (00:00 to 24:00) on the x-axis. The oldest day is at the top, following standard chronobiology convention. Rows are labelled every seven days by default.

plot_actogram(result, tz = TZ)

The dark purple band running through the centre of each row is the main sleep period. The amber gaps are naps; the terracotta blocks are off-wrist episodes.


Double-plotted actogram

plot_actogram_double() uses the classic double-plot format from chronobiology: each recording day appears 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). A dashed vertical line marks the 24 h boundary.

When the sleep band drifts diagonally across rows, that drift reflects day-to-day changes in circadian phase. A stable schedule appears as a straight vertical band; a free-running rhythm traces a slanted line.

plot_actogram_double(result, tz = TZ)


Activity double-plotted actogram

plot_actogram_activity() keeps the double-plot layout but replaces filled tiles with vertical bars. Each bar’s height is proportional to the raw ZCMn activity count; bars are coloured by sleep/wake state. This lets you read both the intensity of activity and the state classification from a single plot.

Bar heights are capped at the 99th percentile of non-zero epochs by default (activity_cap_quantile = 0.99), so a handful of outlier bursts do not compress the rest of the range. Zero-activity epochs (sleep, off-wrist) receive a thin 2% baseline stub so they remain faintly visible rather than disappearing entirely.

plot_actogram_activity(result, tz = TZ)


Colour customisation

Inspecting the default palette

actogram_colours() returns the named hex vector used as the default palette across all three functions. Printing it is the quickest way to see the current defaults before overriding them.

actogram_colours()
#>      wake     sleep       nap off-wrist 
#> "#C25E2A" "#3B2F6B" "#F0A500" "#D9C8A0"

Overriding individual colours

Pass a named character vector to the colours argument. You only need to supply the states you want to change; unnamed states keep the default.

my_cols <- actogram_colours()
my_cols["sleep"]     <- "#1C1A2E"   # darker midnight
my_cols["off-wrist"] <- "#8B4513"   # saddle brown

plot_actogram(result, tz = TZ, colours = my_cols)

Suppressing naps

If your recording has no naps, or you want to simplify the legend, you can drop the nap level by providing only three colours:

plot_actogram(result, tz = TZ,
              colours = c(wake = "#D9C8A0", sleep = "#3B2F6B",
                          "off-wrist" = "#C25E2A"))

Date labels and font size

date_label_every controls how many rows share a single y-axis tick. The default is 7 (one tick per week). Reduce it for shorter recordings or increase it for very long ones.

# Label every 14 days
plot_actogram(result, tz = TZ, date_label_every = 14L)

base_size sets the base font size passed to theme_minimal():

plot_actogram(result, tz = TZ, base_size = 11)   # smaller text
plot_actogram(result, tz = TZ, base_size = 16)   # larger text (presentations)

Adding ggplot2 layers

All three functions return a standard ggplot object, so you can extend them with any ggplot2 layer or theme override:

plot_actogram_double(result, tz = TZ) +
  theme(legend.position = "right")


Working with a bare tibble

The functions accept any tibble with datetime (POSIXct) and state (integer) columns – no pipeline required. This is useful if you have data in a format other than a zeitr_result.

# Subset a few weeks from result$data and plot directly
sub <- result$data[1:5040, ]   # first 3.5 days for illustration
plot_actogram(sub, tz = TZ, title = "First 3.5 days")

The state column should use zeitR’s integer coding: 0 = wake, 1 = sleep, 4 = off-wrist, 7 = nap.


Choosing between the three variants

  • Use plot_actogram() when you want a compact overview – one row per day, simple state raster. Good for reports and presentations.

  • Use plot_actogram_double() when you want to assess circadian phase drift across the recording. The 48 h window makes diagonal drift in the sleep band visible at a glance.

  • Use plot_actogram_activity() when activity intensity matters – for example, when comparing sedentary and active participants, or checking whether an unexpectedly short TST reflects genuine low activity or a classification artefact.


Next steps

  • vignette("sleep-analysis") – CSPD pipeline walkthrough
  • vignette("vallim-pipeline") – Vallim classification rule set
  • ?compute_sleep_metrics, ?compute_cpd_metrics – day-type sleep summary and chronotype metrics
  • ?export_hypnogram – export epoch-level staging to hypnoR
  • ?label_states – convert integer state codes to a human-readable factor