I want to create a plot that looks similar to the figure below, which was created in PowerPoint.
Here is an example data frame containing data for the first two people in the figure.
people <- 1:2
months <- -1:12
pt_df <- expand_grid(person = people, month = months)
rows_per_person <- length(months)
p01 <- rep("Diseased", rows_per_person)
p02 <- c(rep("Pre-follow-up", 2), rep("At Risk", 2), rep("Diseased", 3), rep("Deceased", 7))
status <- c(p01, p02)
pt_df$status <- status
pt_df <- pt_df |>
mutate(
person_f = factor(person),
status_f = factor(status, levels = c("Pre-follow-up", "At Risk", "Diseased", "Deceased"))
)
Attempt 1
My first attempt was to simply create a stacked bar chart.
ggplot(pt_df, aes(person_f, month, fill = status_f)) +
geom_col(position = "stack")
However, position = "stack"
doesn’t "slide" the bars on top of each other. Instead, as is appropriate for the name, it "stacks" the bars on top of each other, which has an additive effect on the y-axis. This is not what I want.
Attempt 2
So, I thought that maybe I should use position = "dodge"
with zero horizontal travel instead.
ggplot(pt_df, aes(person_f, month, fill = status_f)) +
geom_col(width = 0.5, position = position_dodge(0))
This sort of works. As you can see in the figure below, the bars are "sliding" on top of each other as I wanted. I just need to control the depth so that the longest bar (i.e., "Deceased") is sent to the "back" of the stack, followed by "At Risk" and then "Diseased".
Attempt 3
So, I thought I would just reverse the factor order.
pt_df <- pt_df |>
mutate(status_f_rev = forcats::fct_rev(status_f))
ggplot(pt_df, aes(person_f, month, fill = status_f_rev)) +
geom_col(width = 0.5, position = position_dodge(0))
Unfortunately, that doesn’t work either. Deceased is now listed at the top of the legend, but the bar for Deceased is still at the front of the stack. And the bars now appear to be "sliding" in reverse order (Deceased on the left when I use status_f_rev
) as compared to before (Deceased on the right when I use status_f
)
So, it appears as though the factor order affects the ordering of the legend and x-axis, but not the depth (z-axis). Any help is greatly appreciated!
>Solution :
ggplot2
doesn’t really have a concept of a z-axis in terms of "depth." This is probably why it doesn’t follow e.g. factor order (which I agree is a reasonable guess). Instead, you can control the draw order in two ways. First, the layers are drawn in the order they are added to the plot. Second, within each layer, elements are drawn by the order they appear in the data.
In your case, reversing the data order works:
ggplot(pt_df[nrow(pt_df):1, ], aes(person_f, month, fill = status)) +
geom_col(width = 0.5, position = 'identity')
Or you can arrange
first, which is more robust:
pt_df |>
arrange(desc(status_f)) |>
ggplot(aes(person_f, month, fill = status_f)) +
geom_col(width = 0.5, position = 'identity')
(Note that position = 'identity'
is the standard way to set the positions to not stack.)