--- title: "Visualizing Longitudinal Trajectories with geom_kodom_line" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Visualizing Longitudinal Trajectories with geom_kodom_line} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.width = 7, fig.height = 4.2, out.width = "100%" ) library(ggkodom) library(ggplot2) ``` `geom_kodom_line()` draws one horizontal lane per subject, with time on the x-axis and value encoded as a color gradient along each path. It is a direct ggplot2 layer, so it composes freely with `facet_wrap()`, `scale_*()`, `theme()`, and any other ggplot2 building block. ## Sample data All examples use a simulated HbA1c dataset: 25 patients each with 6–12 irregular visits over 24 months. Each patient also has a fixed `age` covariate used later to demonstrate independent aesthetic mappings. ```{r data} set.seed(42) n_subjects <- 25 n_obs_per <- sample(6:12, n_subjects, replace = TRUE) df <- do.call(rbind, lapply(seq_len(n_subjects), function(i) { n <- n_obs_per[i] base <- rnorm(1, mean = 7.5, sd = 1.2) trend <- rnorm(1, mean = -0.02, sd = 0.01) time <- sort(runif(n, 0, 24)) value <- base + trend * time + rnorm(n, sd = 0.4) data.frame( subject_id = sprintf("P%03d", i), visit_month = time, relative_month = time - min(time), hba1c = pmax(4, value), arm = ifelse(i <= 12, "Treatment", "Control"), age = sample(30:75, 1), stringsAsFactors = FALSE ) })) ``` --- ## 1. Basic usage Map `x` to time, `id` to the subject identifier, and `colour` to the measurement value. The stat assigns each unique `id` a lane position on the y-axis automatically. ```{r basic} ggplot(df, aes(x = visit_month, id = subject_id, colour = hba1c)) + geom_kodom_line() + scale_colour_kodom() + labs(x = "Visit (months)", y = "", colour = "HbA1c (%)") + theme_kodom() ``` The color along each path is a **smooth gradient** interpolated between consecutive measurement points, not a step function. Teal = lower HbA1c, red = higher. --- ## 2. Lane ordering with `sort_by` By default lanes follow first-appearance order of subject IDs. Use `sort_by` to re-order by a summary of the `colour` variable. ```{r sort, fig.height = 5} # Top lanes = highest mean HbA1c, bottom = lowest ggplot(df, aes(x = visit_month, id = subject_id, colour = hba1c)) + geom_kodom_line(sort_by = "mean") + scale_colour_kodom() + labs(x = "Visit (months)", y = "", title = 'sort_by = "mean"') + theme_kodom(legend_position = "top") ``` Available `sort_by` values: | Value | Lane order | |---|---| | `"none"` | First appearance in data (default) | | `"mean"` | Descending mean of `colour` | | `"mean_asc"` | Ascending mean of `colour` | | `"first"` | Descending value at earliest timepoint | | `"last"` | Descending value at latest timepoint | Often visits are counted by relative differences with the initial visit for every patients marked at time $0$. This can be achieved by simple transformation of the data. ```{r transform} ggplot(df, aes(x = relative_month, id = subject_id, colour = hba1c)) + geom_kodom_line(sort_by = "first") + scale_colour_kodom() + labs(x = "Visit (months)", y = "", title = 'sort_by = "mean"') + theme_kodom(legend_position = "top") ``` --- ## 3. Limiting subjects with `n_max` For large cohorts, `n_max` takes a random sample of subjects to keep the plot readable. Here we show only 12 of the 25 patients. ```{r n_max, fig.height = 6} ggplot(df, aes(x = visit_month, id = subject_id, colour = hba1c)) + geom_kodom_line(sort_by = "mean", n_max = 12) + scale_colour_kodom() + labs(x = "Visit (months)", y = "", title = "12 randomly sampled subjects") + theme_kodom() ``` --- ## 4. Controlling points `show_points = TRUE` (the default) places a dot at every observation. This makes irregular visit times visible at a glance — critical for sparse clinical data. ```{r points-compare, fig.height = 6} p_base <- ggplot(df, aes(x = visit_month, id = subject_id, colour = hba1c)) + scale_colour_kodom() + labs(x = "Visit (months)", y = "") + theme_kodom() p_base + geom_kodom_line(show_points = TRUE) + labs(title = "show_points = TRUE (default)") ``` ```{r points-off, fig.height = 6} p_base + geom_kodom_line(show_points = FALSE) + labs(title = "show_points = FALSE") ``` You can also suppress points by passing `shape = NA` or `size = 0` as fixed aesthetics — useful when you want to keep the parameter API consistent across geom calls: ```{r points-na} p_base + geom_kodom_line(shape = NA) + labs(title = "shape = NA — same effect as show_points = FALSE") ``` --- ## 5. Customising point appearance `shape`, `size`, `stroke`, and `fill` control the observation markers. These follow standard ggplot2 conventions. ```{r point-shapes, fig.height = 6} # shape 21 = filled circle — fill controls interior, colour controls border ggplot( df, aes(x = visit_month, id = subject_id, colour = hba1c, fill = hba1c) ) + geom_kodom_line(shape = 21, size = 2.5, stroke = 0.6) + scale_colour_kodom() + scale_fill_kodom() + labs(x = "Visit (months)", y = "", title = "shape = 21 with fill + colour") + theme_kodom() ``` ```{r point-diamonds, fig.height = 6} # shape 23 = filled diamond ggplot( df, aes(x = visit_month, id = subject_id, colour = hba1c, fill = hba1c) ) + geom_kodom_line(shape = 23, size = 2.5) + scale_colour_kodom() + scale_fill_kodom() + labs(x = "Visit (months)", y = "", title = "shape = 23 (diamond)") + theme_kodom() ``` --- ## 6. Controlling line appearance `linewidth` sets the path width. `alpha` controls transparency for both line and points. ```{r linewidth, fig.height = 6} ggplot(df, aes(x = visit_month, id = subject_id, colour = hba1c)) + geom_kodom_line(linewidth = 3, alpha = 0.5) + scale_colour_kodom() + labs(x = "Visit (months)", y = "", title = "linewidth = 3, alpha = 0.5") + theme_kodom() ``` --- ## 7. Discrete color bands with `scale_colour_kodom(discretize = TRUE)` Continuous gradients can wash out when values cluster tightly. `discretize = TRUE` switches to solid color bands, and `color_breaks` sets the clinical thresholds. ```{r discrete, fig.height = 6} ggplot(df, aes(x = visit_month, id = subject_id, colour = hba1c)) + geom_kodom_line(sort_by = "mean") + scale_colour_kodom( discretize = TRUE, color_breaks = c(5.7, 6.5, 8), name = "HbA1c (%)" ) + labs(x = "Visit (months)", y = "", title = "Discrete clinical bands") + theme_kodom() ``` --- ## 8. Independent `size` and `linewidth` — encoding patient covariates `size` controls **only the observation markers**; `linewidth` controls **only the connecting path**. They are independently routed to `GeomPoint` and `GeomSegment` respectively, so mapping one has no effect on the other. Use this to encode a fixed patient-level covariate (here, `age`) as point size while keeping the path width constant: ```{r size-age} ggplot(df, aes( x = visit_month, id = subject_id, colour = hba1c, size = age )) + geom_kodom_line(sort_by = "mean", linewidth = 0.4) + scale_colour_kodom() + scale_size_continuous(range = c(1, 4), name = "Age") + labs( x = "Visit (months)", y = "", title = "Point size = patient age, path width fixed" ) + theme_kodom() ``` `alpha` works the same way — here it encodes age as opacity, making older patients' trajectories more prominent: ```{r alpha-age} ggplot(df, aes( x = visit_month, id = subject_id, colour = hba1c, alpha = age )) + geom_kodom_line(sort_by = "mean") + scale_colour_kodom() + scale_alpha_continuous(range = c(0.2, 1), name = "Age") + labs( x = "Visit (months)", y = "", title = "Opacity = patient age" ) + theme_kodom() ``` Both can be combined — size and alpha each independently driven by a covariate: ```{r size-alpha-age} ggplot(df, aes( x = visit_month, id = subject_id, colour = hba1c, size = age, alpha = age )) + geom_kodom_line(sort_by = "mean", linewidth = 0.3) + scale_colour_kodom() + scale_size_continuous(range = c(1, 4), name = "Age") + scale_alpha_continuous(range = c(0.25, 1), guide = "none") + labs( x = "Visit (months)", y = "", title = "Size + opacity both encode age" ) + theme_kodom() ``` --- ## 10. Faceting by a grouping variable Because `geom_kodom_line()` is a standard ggplot2 layer, `facet_wrap()` and `facet_grid()` work out of the box. Lane ordering is computed independently within each facet panel. ```{r facet, fig.height = 6} ggplot(df, aes(x = visit_month, id = subject_id, colour = hba1c)) + geom_kodom_line(sort_by = "mean") + scale_colour_kodom(color_breaks = c(5.7, 6.5, 8)) + facet_wrap(~arm) + labs(x = "Visit (months)", y = "", title = "Treatment vs. Control") + theme_kodom() ``` --- ## 11. Y-axis subject labels The y-axis shows integer lane numbers by default. Use `scale_y_continuous()` to replace them with subject IDs, or `theme()` to suppress them entirely. ```{r y-labels, fig.height = 6} # Suppress y labels for large cohorts — usually the right choice ggplot(df, aes(x = visit_month, id = subject_id, colour = hba1c)) + geom_kodom_line(sort_by = "mean") + scale_colour_kodom() + labs(x = "Visit (months)", y = "") + theme_kodom() + theme(axis.text.y = element_blank()) ``` --- ## 12. Full palette reference ```{r palette, fig.height = 4} # View the three anchor colors and any interpolated expansion scales::show_col(kodom_colors(7)) ``` `kodom_colors()` returns the three anchors (teal `#008D98`, gold `#FFCC3D`, red `#D7433B`). Passing `n > 3` interpolates between them via `colorRampPalette`.