Wonderful Wednesday April 2025 (61)

Markov trace Longitudinal data Wonderful Wednesdays

Measuring treatment response is vital for the assessment of the efficacy of a drug. Visualisations can help to show response gain and maintenance or loss over time.

PSI VIS SIG https://www.psiweb.org/sigs-special-interest-groups/visualisation
04-09-2025

Markov Trace

Data set:

The Challenge:

Visualise the efficacy of the two treatments with respect to state occupancy over time.

Options:

A description of the challenge can also be found here.
A recording of the session can be found here.

Visualisations

Animation of plots over time is a very intuitive way to show development of the results over the course of the study. Lasagna plots allow seeing individual patient data, but then right sorting is crucial. Sankey diagrams make it easy to compare proportions over time. This can also be achieved with stacked area charts with careful interpretation of the intermediate stages.

Example 1. Animated bar chart

link to code

Example 2. Animated line plot

link to code

Example 3. Static line plot

link to code

Example 4. Heat map

Example 5. Sankey diagrams


Example 6. Stacked area charts

Example 4. Flowing data (non-pharma)


Source: Flowing Data

Code

Example 1. Animated bar chart

#######################
# Author: Harry Wykes #
#######################

if(!require("pacman")) install.packages("pacman")
p_load("tidyverse", "this.path", "readxl", "gganimate", "gifski")

setwd(this.dir())
dat<-read_xlsx("WWW_APR2025.xlsx", 1) %>%
  mutate(AVALC = reorder(AVALC, AVAL, decreasing = TRUE))

bign <- dat %>%
  filter(AVISITN==1) %>%
  group_by(TRT) %>%
  mutate(N=n()) %>%
  select(TRT, N) %>%
  distinct()

#Proportion of subjects at each level at each time
prop <- dat %>%
  merge(bign) %>%
  group_by(TRT, AVISITN, AVALC) %>%
  mutate(count = n(), prp=count/N, perc = round(100*prp, 2)) %>%
  select(TRT, AVISITN, AVALC, AVAL, count, prp, perc) %>%
  distinct()

#Animated bar plot
bar<-prop %>%
  filter(AVISITN != 1) %>%
  ggplot(aes(x=reorder(AVALC, AVAL), y=perc, group=TRT, fill=TRT)) +
  geom_bar(stat="identity", position="dodge") +
  ylim(c(0,60)) +
  transition_time(AVISITN) +
  enter_grow() +
  exit_fade() +
  labs(x="Response", 
       y="Percentage of Responsders", 
       title="Visit: {frame_time}",
       fill = "Treatment") +
  theme(axis.text = element_text(size=20),
        axis.title = element_text(size=30),
        plot.title = element_text(size=40),
        legend.text = element_text(size=30),
        legend.title = element_text(size=30),
        legend.key.size = unit(2, "cm"))

animate(bar, nframes=19, fps=1, width=1920, height=1080, renderer=gifski_renderer())
anim_save("bar.gif")

Back to blog

Example 2. Animated line plot

#######################
# Author: Harry Wykes #
#######################

if(!require("pacman")) install.packages("pacman")
p_load("tidyverse", "this.path", "readxl", "gganimate", "gifski")

setwd(this.dir())
dat<-read_xlsx("WWW_APR2025.xlsx", 1) %>%
  mutate(AVALC = reorder(AVALC, AVAL, decreasing = TRUE))

bign <- dat %>%
  filter(AVISITN==1) %>%
  group_by(TRT) %>%
  mutate(N=n()) %>%
  select(TRT, N) %>%
  distinct()

#Proportion of subjects at each level at each time
prop <- dat %>%
  merge(bign) %>%
  group_by(TRT, AVISITN, AVALC) %>%
  mutate(count = n(), prp=count/N, perc = round(100*prp, 2)) %>%
  select(TRT, AVISITN, AVALC, AVAL, count, prp, perc) %>%
  distinct()

#Animated moving line plot
line<-prop %>%
  filter(AVISITN != 1) %>%
  ggplot(aes(x=AVISITN, y=prp, col=TRT)) +
  geom_point(size=10) +
  geom_line(linewidth=5) +
  facet_wrap(~AVALC) +
  scale_x_continuous(breaks=c(2,5,10,15,20)) +
  ylim(c(0,0.6)) +
  transition_reveal(AVISITN) +
  ease_aes("sine-in-out") +
  labs(x="Visit", 
       y="Proportion of Patients", 
       title="Visit: {frame_along}",
       col="Treatment") +
  theme(axis.text = element_text(size=20),
        axis.title = element_text(size=30),
        plot.title = element_text(size=40),
        legend.text = element_text(size=30),
        legend.title = element_text(size=30),
        legend.key.size = unit(2, "cm"),
        strip.text = element_text(size=30))

animate(line, nframes=19, fps=1.9, width=1920, height=1080, renderer=gifski_renderer())
anim_save("line.gif")

Back to blog

Example 3. Animated bar chart

#######################
# Author: Harry Wykes #
#######################

if(!require("pacman")) install.packages("pacman")
p_load("tidyverse", "this.path", "readxl", "gganimate", "gifski")

setwd(this.dir())
dat<-read_xlsx("WWW_APR2025.xlsx", 1) %>%
  mutate(AVALC = reorder(AVALC, AVAL, decreasing = TRUE))

bign <- dat %>%
  filter(AVISITN==1) %>%
  group_by(TRT) %>%
  mutate(N=n()) %>%
  select(TRT, N) %>%
  distinct()

#Proportion of subjects at each level at each time
prop <- dat %>%
  merge(bign) %>%
  group_by(TRT, AVISITN, AVALC) %>%
  mutate(count = n(), prp=count/N, perc = round(100*prp, 2)) %>%
  select(TRT, AVISITN, AVALC, AVAL, count, prp, perc) %>%
  distinct()

#Non-animated fitted line facet plots
line_static<-prop %>%
  filter(AVISITN != 1) %>%
  ggplot(aes(x=AVISITN, y=perc, group=TRT, col=TRT)) +
  geom_point(alpha=0.5) +
  geom_smooth(method="lm", se=FALSE) +
  xlim(0,20) +
  ylim(0,60) +
  xlab("Visit Number") +
  ylab("% of subjects") +
  labs(col="Treatment") +
  facet_wrap(~AVALC) +
  theme(panel.grid.minor = element_blank())

ggsave("line_static.png", line_static, width=1920, height=1080, units="px")

Back to blog

Citation

For attribution, please cite this work as

SIG (2025, April 9). VIS-SIG Blog: Wonderful Wednesday April 2025 (61). Retrieved from https://graphicsprinciples.github.io/posts/2025-04-09-wonderful-wednesday-april-2025/

BibTeX citation

@misc{sig2025wonderful,
  author = {SIG, PSI VIS},
  title = {VIS-SIG Blog: Wonderful Wednesday April 2025 (61)},
  url = {https://graphicsprinciples.github.io/posts/2025-04-09-wonderful-wednesday-april-2025/},
  year = {2025}
}