Așadar, o discuție critică a parcelelor Sankey a plutit recent pe feed -ul meu cu Bluesky, iar o replică a inclus un exemplu urât și comentariul „Oricine care a crezut că această ilustrare a îmbunătățit claritatea trăiește într -o realitate alternativă”. Graficul propriu -zis pe care l -am inclus în partea de jos a acestei postări. Sunt de acord că este destul de inutil, dar am crezut că am văzut potențial și am spus -o. Acest blog văd dacă, de fapt, se poate face ceva cu el.
Postarea care a început discuția a fost Emily Moin spunând că „Diagramele Sankey sunt la fel de proaste ca graficele de plăcinte și, de asemenea, mai rău, deoarece înțelepciunea convențională nu le -a respins încă, așa că trebuie să le privesc”. Așa cum se întâmplă, cred că chiar și diagramele de plăcintă au locul lor (foarte limitat), atât timp cât sunt atenți la aleasă (nu atunci când prea multe categorii, de exemplu) și lustruite corespunzător pentru public. Așadar, cred că sunt consecvent în a crede că diagramele Sankey pot fi de asemenea utile.
Iată ce cred că este greșit cu graficul original (care este reprodus mai târziu în acest post), care cred că urmărește progresia pacienților care se confruntă cu simptome de severitate variabilă pe o perioadă de șase săptămâni:
- Etichetele înghesuie imaginea și au o mulțime de informații redundante repetate de mai multe ori („Săptămâna severității…”)
- Săptămânile și severitatea sunt măsurate în număr și sunt prezentate împreună pe etichete, ceea ce face o sarcină cognitivă semnificativă pentru a analiza etichetele („Săptămâna severității 0: 3” depune eforturi pentru a rezolva severitatea este 3, iar săptămâna este 0, ceea ce este exact genul de lucruri pe care doriți să le intuiți direct din poziție sau culoare într -un complot, mai degrabă decât să o citiți)
- Culorile nu sunt prietenoase cu culori.
- Deși culorile sunt mapate în mod corespunzător la scala de severitate (albastru pentru o severitate scăzută, verde până la mijlocul și roșu pentru înălțime), nu există nicio legendă care să atragă acest lucru în atenția cititorului și din cauza ordinului de panglici pe pagină (vezi punctul următor), această secvențiere a culorilor nu este niciodată evidentă pentru cititor.
- Nodurile reprezentând severitatea într -o săptămână dată nu sunt în nicio ordine fixă pe pagină și se schimbă de la săptămână la săptămână. Se pare că au fost aleși mai mult pentru a obține nivelurile de severitate cu mai mulți pacienți în centrul graficului. Acest lucru oprește cititorul să obțină orice lectură ușoară a severității (care ar fi putut fi mapată în poziția verticală în zona de grafic) și se adaugă la senzația înfundată și complexă a complotului – de exemplu, având panglică albastră pentru severitatea 2 sărind din partea inferioară a parcelei în săptămânile 0 și 1 în vârf în săptămânile 3 și 6.
Va trebui să coborâți puțin în postare pentru a vedea acea grafică originală; Veți vedea că efectul combinat al acestor probleme este într -adevăr unul de complexitate și dezordine. Cred că ultimul punct – nodurile de severitate care schimbă se plasează vertical – este cel mai important.
Am avut un lucru la îmbunătățirea acestui lucru și am venit cu câteva alternative, folosind ale lui David Sjoberg ggsankey
Pachet R. Iată o versiune de complot Sankey:
… și iată o versiune de complot aluvial. Parcele aluviale sunt similare cu parcelele Sankey, dar nu au spații între noduri, ceea ce înseamnă că în acest caz puteți citi nodurile pe verticală în fiecare săptămână în mod similar cu un grafic de bare stivuite:
Iată graficul original pentru comparație:
Sunt destul de încrezător că fie Sankey, fie complotul aluvial sunt îmbunătățiri definite și dau un sens mai bun al severității medii în fiecare săptămână și tendința generală (care este mai albastru, cazuri de severitate scăzute). În timp ce încă dau un sentiment de oameni care se deplasează în mai multe direcții (uneori în sus) din fiecare combinație de severitate. Deci cred că am abordat principalele puncte aici:
- A declanșat etichetele având etichete de axe pentru „săptămâna zero”, „săptămâna unu”, etc; În sensul că nu trebuie să repetăm acest lucru în fiecare nod. Iar eticheta nodului este acum doar numărul unic de severitate.
- A evitat încărcătura cognitivă a săptămânii și severitatea, ambele fiind numere, parțial de etichetele simplificate de mai sus și parțial prin scrierea săptămânilor în cuvinte în limba engleză (una, trei, etc.), mai degrabă decât cifre.
- A ales o paletă mai prietenoasă cu culoare-orb, bazată pe schema de blu nu roșu-galben, mai degrabă decât pe roșu-roșu-albastru-albastru
- Încă nu am o legendă, dar cred că acum este mult mai clar pentru cititor că roșul este severitate ridicată și albastru este scăzut, din cauza secvenției verticale a nodurilor …
- … Care este principala soluție aici – am comandat cu strictețe nodurile de severitate de 1,2,3,4,5,6,7, astfel încât să nu schimbe niciodată pozițiile. Aceasta înseamnă mai puțin încrucișarea fasciculelor și, prin urmare, mai puțină senzație de complexitate în complot. Cel mai important, oferă ochiului o modalitate ușoară de a judeca proporția de oameni în fiecare nivel de severitate prin poziție și dimensiune verticală pe pagină.
Am lăsat tot codul la sfârșit, deoarece cea mai mare parte a fost despre mine încercând să pun la punct de mână un set de date care seamănă cu cel din graficul de flux original. Apoi a trebuit să-l calibrez în R pentru a rezolva problemele din versiunea mea făcută de mână. Aceasta a inclus probleme precum numărul de oameni care se schimbă de la săptămână la săptămână, iar numărul de persoane care intră într -un anumit stat de severitate într -o săptămână, nu se potrivesc cu numărul care îi iese. Odată ce aceste lucruri sunt tratate, desenarea complotului este relativ simplu ggplot2
şi ggsankey
bucată de cod.
library(tidyverse)
library(janitor)
library(glue)
library(RColorBrewer)
remotes::install_github("davidsjoberg/ggsankey")
library(ggsankey) # one fairly straightforward approach to sankey charts / flow diagrams
# read in some data. This was very crudely hand-entered with some
# rough visual judgements based from a chart that I don't know the
# origin of I saw on the internet. So treat as made-up example data:
d <- read_csv("https://raw.githubusercontent.com/ellisp/blog-source/refs/heads/master/data/complicated-sankey-data.csv",
col_types = "ccccd",
# we want the NAs in the original to be characters, not actual NA:
na = "missing") |>
clean_names()
#------------tidying up data---------------
# we have some adjustments to deal with because of having made up data
# An extra bunch of rows of data that are needed by the Sankey function to
# to make the week 6 nodes show up:
extras <- d |>
filter(week_to == "6") |>
mutate(
week_from = "6",
week_to = NA,
severity_from = severity_to)
#' Convenience relabelling function for turning week numbers into a factor:
weekf <- function(x){
x <- case_when(
x == 0 ~ "Week zero",
x == 1 ~ "Week one",
x == 3 ~ "Week three",
x == 6 ~ "Week six"
)
x <- factor(x, levels = c("Week zero","Week one","Week three", "Week six"))
}
# going to start by treating all flow widths as proportions
total_people <- 1
# add in the extra data rows to show the final week of nodes,
# and relabel the weeks:
d2 <- d |>
rbind(extras) |>
mutate(week_from = weekf(week_from),
week_to = weekf(week_to))
# there should be the same total number of people each week,
# and the same number of people leaving each "node" (a severity-week
# combination) as arrived at it on the flow from the last week.
# we have a little iterative process to clean this up. If we had
# real data, none of this would be necessary; this is basically
# because I made up data with some rough visual judgements:
for(i in 1:5){
# scale the data so the population stays the same week by week
# (adds up to total_people, which is 1, so are proportions)
d2 <- d2 |>
group_by(week_from) |>
mutate(value = value / sum(value) * total_people) |>
group_by(week_to) |>
mutate(value = value / sum(value) * total_people) |>
ungroup()
# how many people arrived at each node (week-severity combination)?
tot_arrived <- d2 |>
group_by(week_from, severity_from) |>
mutate(arrived_sev_from = sum(value)) |>
group_by(week_to, severity_to) |>
mutate(arrived_sev_to = sum(value)) |>
ungroup() |>
distinct(week_to, severity_to, arrived_sev_to)
# scale the data leaving the node to match what came in:
d2 <- d2 |>
left_join(tot_arrived, by = c("week_from" = "week_to",
"severity_from" = "severity_to")) |>
group_by(week_from, severity_from) |>
mutate(value = if_else(is.na(arrived_sev_to), value,
value / sum(value) * unique(arrived_sev_to)) ) |>
select(-arrived_sev_to) |>
ungroup()
}
# manual check - these should all be basically the same numbers
filter(tot_arrived, week_to == "Week one" & severity_to == 4)
filter(d2, week_to == "Week one" & severity_to == 4) |> summarise(sum(value))
filter(d2, week_from == "Week one" & severity_from == 4) |> summarise(sum(value))
#--------------draw plot-------------
# palette that is colourblined-ok and shows sequence. This
# actually wasn't too bad in the original, but it got lost
# in the vertical shuffling of all the severity nodes:
pal <- c("grey", brewer.pal(7, "RdYlBu")(7:1))
names(pal) <- c("NA", 1:7)
# Draw the actual chart. First, the base of chart, common to both:
p0 <- d2 |>
mutate(value = round(value * 1000)) |>
uncount(weights = value) |>
mutate(severity_from = factor(severity_from, levels = c("NA", 1:7)),
severity_to = factor(severity_to, levels = c("NA", 1:7))) |>
ggplot(aes(x = week_from,
next_x = week_to,
node = severity_from,
next_node = severity_to,
fill = severity_from,
label = severity_from)) +
# default has a lot of white space between y axis and the data
# so reduce the expansion of x axis to reduce that
scale_x_discrete(expand = c(0.05, 0)) +
scale_fill_manual(values = pal) +
labs(subtitle = "Chart is still cluttered, but decreasing severity over time is apparent.
To achieve this, vertical sequencing is mapped to severity, and repetitive labels have been moved into the axis guides.",
x = "",
caption = "Data has been hand-synthesised to be close to an original plot of unknown provenance.")
# Sankey plot:
p1 <- p0 +
geom_sankey(alpha = 0.8) +
geom_sankey_label() +
theme_sankey(base_family = "Roboto") +
theme(legend.position = "none",
plot.title = element_text(family = "Sarala")) +
labs(title = "Severity of an unknown disease shown in a Sankey chart")
# Alluvial plot:
p2 <- p0 +
geom_alluvial(alpha = 0.8) +
geom_alluvial_label() +
theme_alluvial(base_family = "Roboto") +
theme(legend.position = "none",
plot.title = element_text(family = "Sarala")) +
labs(title = "Severity of an unknown disease shown in an alluvial chart",
y = "Number of people")
print(p1) # Sankey plot
print(p2) # alluvial plot