Longitudinal Visualisation of DASS21 Subscale Scores Across a Simulated Cohort

Author

Julian Chung

1 Introduction

The Depression Anxiety Stress Scales (DASS21) is a self-report instrument designed to measure the emotional states of depression, anxiety, and stress. The short-form DASS21 contains 21 items divided evenly across the three subscales. Each item is scored from 0 to 3, and subscale totals are multiplied by 2 to align with the original DASS42 severity classification thresholds. This chart tracks individual subscale scores across five timepoints (baseline, 3, 6, 9, and 12 months) using simulated intervention and control data.

2 Simulation

The dataset was generated in Python: 20 participants per group, scored across five timepoints.

3 Data Preparation

The subscale scores are first multiplied by 2 to align with DASS42 scoring conventions. The dataset is then reshaped into long format to enable visualisation of changes across timepoints and subscales.

Show code
suppressPackageStartupMessages(library(tidyverse))

# Load data
data <- read.csv(here::here("data", "simulated_dass21_full.csv"))

# Multiply subscale scores by 2 to match DASS42 scoring conventions
scored_data <- data %>%
  mutate(across(c(DASS_Anxiety, DASS_Depression, DASS_Stress), ~ .x * 2))

# Reshape to long format
dass_long <- scored_data %>%
  pivot_longer(cols = c(DASS_Anxiety, DASS_Depression, DASS_Stress),
              names_to = "subscale",
              values_to = "score") %>%
  mutate(timepoint = factor(timepoint, levels = c("baseline", "3_months", "6_months", "9_months", "12_months")))

4 Severity Classification

The DASS21 measures three subscales: Depression, Anxiety, and Stress. After summing item responses and multiplying scores by 2, each subscale can be categorised into severity bands based on validated thresholds.

The severity bands are:

Subscale Normal Mild Moderate Severe Extremely Severe
Depression 0–9 10–13 14–20 21–27 28+
Anxiety 0–7 8–9 10–14 15–19 20+
Stress 0–14 15–18 19–25 26–33 34+
Show code
classify_severity <- function(score, subscale) {
  if (subscale == "DASS_Depression") {
    cut(score, breaks = c(-Inf, 9, 13, 20, 27, Inf),
        labels = c("Normal", "Mild", "Moderate", "Severe", "Extremely Severe"), right = TRUE)
  } else if (subscale == "DASS_Anxiety") {
    cut(score, breaks = c(-Inf, 7, 9, 14, 19, Inf),
        labels = c("Normal", "Mild", "Moderate", "Severe", "Extremely Severe"), right = TRUE)
  } else if (subscale == "DASS_Stress") {
    cut(score, breaks = c(-Inf, 14, 18, 25, 33, Inf),
        labels = c("Normal", "Mild", "Moderate", "Severe", "Extremely Severe"), right = TRUE)
  } else {
    return(NA)
  }
}

dass_long <- dass_long %>%
  mutate(severity_band = mapply(classify_severity, score, subscale))

5 Visualisation: Intervention Group

This chart shows how individual participants’ subscale scores can be tracked across severity bands over time. By visualising Depression, Anxiety, and Stress trajectories separately, it becomes possible to assess how a treatment impacts specific psychological domains and detect patterns that might be masked in a total DASS21 score.

Show code
intervention_data <- dass_long %>% filter(group == "intervention")

# Define timepoint labels
timepoint_labels <- c(
  "baseline" = "Baseline",
  "3_months" = "3 Months",
  "6_months" = "6 Months",
  "9_months" = "9 Months",
  "12_months" = "12 Months"
)

intervention_data$severity_band <- factor(intervention_data$severity_band,
                                          levels = c("Normal", "Mild", "Moderate", "Severe", "Extremely Severe"))

ggplot(intervention_data, aes(x = severity_band, y = timepoint, colour = subscale)) +
  geom_path(aes(group = interaction(id, subscale)),
            position = position_dodge(width = 0.4), alpha = 0.7) +
  geom_point(aes(group = interaction(id, subscale)),
            position = position_dodge(width = 0.4),
            size = 1.5, shape = 21, fill = "white", stroke = 0.3, alpha = 0.9) +
  scale_colour_manual("Subscale",
                      values = c("DASS_Anxiety" = "#E23418",
                                "DASS_Depression" = "#184CE2",
                                "DASS_Stress" = "#03AC50"),
                      labels = c("Anxiety", "Depression", "Stress")) +
  scale_y_discrete(labels = timepoint_labels) +
  coord_flip() +
  facet_wrap(~id) +
  theme_bw() +
  labs(
    title = "Visualising Longitudinal DASS21 Severity\nby Subscale (Intervention Group)",
    x = "Severity Band",
    y = "Timepoint"
  ) +
  theme(
    axis.text.y = element_text(angle = 0, hjust = 1),
    axis.text.x = element_text(angle = 45, size = 8, hjust = 1, margin = margin(t = 10)),
    plot.title = element_text(hjust = 0.5, face = "bold", margin = margin(b = 10)),
    strip.text = element_text(size = 8),
    legend.background = element_rect(fill = "white", colour = "black", linewidth = 0.5),
    legend.title = element_text(hjust = 0.5),
    legend.key = element_rect(fill = "white", colour = NA),
    legend.spacing.y = unit(5, "pt")
  )

6 Visualisation: Control Group

The control group trajectories remain comparatively stable, contextualising the change observed in the intervention cohort.

Show code
control_data <- dass_long %>% filter(group == "control")

control_data$severity_band <- factor(control_data$severity_band,
                                    levels = c("Normal", "Mild", "Moderate", "Severe", "Extremely Severe"))

ggplot(control_data, aes(x = severity_band, y = timepoint, colour = subscale)) +
  geom_path(aes(group = interaction(id, subscale)),
            position = position_dodge(width = 0.4), alpha = 0.7) +
  geom_point(aes(group = interaction(id, subscale)),
            position = position_dodge(width = 0.4),
            size = 1.5, shape = 21, fill = "white", stroke = 0.3, alpha = 0.9) +
  scale_colour_manual("Subscale",
                      values = c("DASS_Anxiety" = "#E23418",
                                "DASS_Depression" = "#184CE2",
                                "DASS_Stress" = "#03AC50"),
                      labels = c("Anxiety", "Depression", "Stress")) +
  scale_y_discrete(labels = timepoint_labels) +
  coord_flip() +
  facet_wrap(~id) +
  theme_bw() +
  labs(
    title = "Visualising Longitudinal DASS21 Severity\nby Subscale (Control Group)",
    x = "Severity Band",
    y = "Timepoint"
  ) +
  theme(
    axis.text.y = element_text(angle = 0, hjust = 1),
    axis.text.x = element_text(angle = 45, size = 8, hjust = 1, margin = margin(t = 10)),
    plot.title = element_text(hjust = 0.5, face = "bold", margin = margin(b = 10)),
    strip.text = element_text(size = 8),
    legend.background = element_rect(fill = "white", colour = "black", linewidth = 0.5),
    legend.title = element_text(hjust = 0.5),
    legend.key = element_rect(fill = "white", colour = NA),
    legend.spacing.y = unit(5, "pt")
  )

7 Summary

Faceting by participant ID gives a quick way to scan across a cohort and see who is improving, stable, or worsening, and in which domain specifically. This is more informative than reporting a single total score, since deterioration in one subscale can be masked when scores are aggregated.

8 References

Depression Anxiety Stress Scale-21 (DASS21)

DASS-21 Scoring template and interpretation

Manual for the Depression Anxiety Stress Scales. (2nd. Ed.) Sydney: Psychology Foundation.


This project was derived from work on a real clinical trial dataset, modified here with synthetic data for demonstration. It showcases the workflow in Python, R, Quarto, and ggplot2 for longitudinal visualisation.