Serpentine (snake) plots for survey responses, experience sampling (EMA/ESM) data, and activity timelines using base R graphics. Zero external dependencies.
# From GitHub.
devtools::install_github("mohsaqr/snakeplot")
# Once on CRAN
install.packages("snakeplot")Snake plots arrange data as horizontal bands in a serpentine layout — each row reverses direction and connects to the next through a U-turn arc. The package provides six plotting functions and 10 built-in color palettes:
| Function | Purpose |
|---|---|
survey_snake() |
Survey/EMA responses with ticks, bars, correlations, faceting |
activity_snake() |
Daily activity timelines with event blocks or rug ticks |
sequence_snake() |
State sequence as colored blocks flowing through serpentine layout |
timeline_snake() |
Career/life-event timeline from a 3-column data.frame (role, start, end) |
survey_sequence() |
Stacked 100% horizontal bars in serpentine layout |
sequential_dist() |
Sequential (monochrome) variant of
survey_sequence() |
line_snake() |
Continuous intensity line plot (experimental) |
facet_snake() |
Generic multi-panel wrapper for any snake function |
Three datasets from Neubauer & Schmiedek (2024) are included:
| Dataset | Rows | Description |
|---|---|---|
ema_emotions |
280 | Person-level means for 10 emotions (1-7 scale) |
student_survey |
280 | 34 items across 4 constructs, prefixed for faceting |
ema_beeps |
11 474 | Beep-level timestamps + anger/happiness ratings (14 days) |
library(snakeplot)
labs7 <- c("1" = "Not at all", "2" = "Slightly", "3" = "Somewhat",
"4" = "Moderate", "5" = "Quite", "6" = "Very",
"7" = "Extremely")survey_snake()
— daily value distribution tickssurvey_snake(ema_beeps, var = "angry", day = "day",
colors = snake_palettes$ocean, level_labels = labs7,
title = "Anger — 14 days, value distribution")
survey_snake() —
correlation arcssurvey_snake(ema_emotions, tick_shape = "line",
arc_fill = "correlation", sort_by = "mean",
colors = snake_palettes$ocean, level_labels = labs7,
title = "Emotions — correlations at U-turns")
survey_snake() —
dot plot with dark bandssurvey_snake(ema_emotions, tick_shape = "dot", sort_by = "mean",
colors = snake_palettes$ocean, level_labels = labs7,
band_palette = c("#1a1228", "#1a2a42"),
title = "Emotions — dots on dark bands")
survey_snake() —
faceted multi-constructsurvey_snake(student_survey, facet = TRUE, facet_ncol = 2L,
tick_shape = "bar", sort_by = "mean",
colors = snake_palettes$ocean, level_labels = labs7)
survey_snake() —
daily distribution barssurvey_snake(ema_beeps, var = "happy", day = "day",
tick_shape = "bar", bar_reverse = TRUE,
colors = snake_palettes$ocean, level_labels = labs7,
title = "Happiness — 14 days, distribution bars")
activity_snake() — rug
ticksset.seed(42)
days <- c("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun")
d <- data.frame(
day = rep(days, each = 40),
start = round(runif(280, 360, 1400)),
duration = 0
)
activity_snake(d)
activity_snake() —
duration blocksd2 <- data.frame(
day = rep(days, each = 8),
start = round(runif(56, 360, 1200)),
duration = round(runif(56, 15, 120))
)
activity_snake(d2, event_color = "#e09480", band_color = "#3d2518")
sequence_snake() —
state sequenceset.seed(42)
verbs <- c("Read", "Write", "Discuss", "Listen",
"Search", "Plan", "Code", "Review")
seq75 <- character(0)
while (length(seq75) < 75) {
seq75 <- c(seq75, rep(sample(verbs, 1), sample(1:4, 1)))
}
seq75 <- seq75[seq_len(75)]
sequence_snake(seq75, title = "75-step learning sequence")
timeline_snake() —
career/event timelinecareer <- data.frame(
role = c("Intern", "Junior Dev", "Mid Dev",
"Senior Dev", "Tech Lead", "Architect"),
start = c("2015-01", "2015-07", "2017-01",
"2019-07", "2022-07", "2024-01"),
end = c("2015-06", "2016-12", "2019-06",
"2022-06", "2023-12", "2024-12")
)
timeline_snake(career,
title = "Software Engineer — Career Path (2015-2024)")
survey_sequence() —
stacked barssurvey_sequence(ema_emotions, colors = snake_palettes$ocean)
line_snake() —
continuous intensityset.seed(42)
hours <- seq(0, 1440, by = 10)
d_line <- data.frame(
day = rep(c("Mon", "Tue", "Wed", "Thu", "Fri"), each = length(hours)),
time = rep(hours, 5),
value = sin(rep(hours, 5) / 1440 * 4 * pi) * 50 + 50 +
rnorm(5 * length(hours), 0, 8)
)
line_snake(d_line, fill_color = "#e74c3c")
10 palettes ship with the package — 5 diverging, 5 sequential:
names(snake_palettes)
#> "classic" "earth" "ocean" "sunset" "berry" "blues" "greens" "grays" "warm" "viridis"
# Use directly
survey_snake(ema_emotions, colors = snake_palettes$earth, tick_shape = "bar")
# Interpolate to any length
snake_palette("sunset", n = 5)survey_snake()| Parameter | Description |
|---|---|
tick_shape |
"line" (default), "dot", or
"bar" (stacked proportional) |
sort_by |
"none", "mean", or "net" |
arc_fill |
"none" (two-tone), "correlation",
"mean_prev", "blend" |
colors |
Custom color palette or snake_palettes$name |
band_palette |
2+ anchor colors for band shading (default: brown-to-slate) |
bar_reverse |
TRUE to draw bars from highest level first |
level_labels |
Named vector mapping levels to display labels |
facet |
TRUE (auto-group by prefix) or named list of column
groups |
var, day, timestamp |
Auto-pivot EMA data into daily bands |
show_mean, show_median |
Toggle diamond/dashed-line markers |
The bundled datasets are derived from:
Neubauer, A. B., & Schmiedek, F. (2024). Approaching academic adjustment on multiple time scales. Zeitschrift fuer Erziehungswissenschaft, 27(1), 147-168. https://doi.org/10.1007/s11618-023-01182-8
MIT