Kapitel 70 ggplot Balkengraphiken mit N und Prozenten

Das Erstellen von Balkengraphiken ist im Prinzip relativ einfach, doch die korrekte Beschriftung der Balken mit absoluten und relativen Häufigkeiten kann etwas komplexer sein.

Achtung: Wir erstellen hier nur einfache Graphiken, wir verzichten hier auf das Verschönern der Graphiken - was wir für eine Veröffentlichung jeweils sicher noch tun müssten.

Zum Thema Verschönern von Graphiken gibt es später ein eigenes Kapitel.

70.0.1 Pakete

library(ggplot2)
library(dplyr)
library(stringr)
library(tidyr)
library(arsenal)
library(questionr)

70.0.2 Daten aus veröffentlichtem Artikel

Für dieses Beispiel nutzen wir Daten aus folgenden veröffentlichten Artikel: Sullivan, A., Murray, E. J., & Corlin, L. (2022). Academic training of authors publishing in high-impact epidemiology and clinical journals. PloS one, 17(7), e0271159..

data<-rio::import("https://doi.org/10.1371/journal.pone.0271159.s002", format="csv")
DT::datatable(data, filter='top',options = list(
  pageLength=10, scrollX='800px'))

70.1 Barpplot mit nur einer möglichen Antwortskategorie

Wenn wir eine Frage haben, bei der die Teilnehmenden nur eine Antwort ankreuzen konnten, ist die Darstellung relativ einfach.

Zuerst sollten wir uns einen Überblick über die Daten erschaffen.

summarytools::freq(data$journal_type)
## Frequencies  
## data$journal_type  
## Type: Character  
## 
##                            Freq   % Valid   % Valid Cum.   % Total   % Total Cum.
## ------------------------ ------ --------- -------------- --------- --------------
##             Epidemiology     36     34.95          34.95     34.95          34.95
##         General clinical     42     40.78          75.73     40.78          75.73
##       Specialty clinical     25     24.27         100.00     24.27         100.00
##                     <NA>      0                               0.00         100.00
##                    Total    103    100.00         100.00    100.00         100.00
ggplot(data, aes(x=journal_type, y=""))+
  geom_col(stat='count')
## Warning in geom_col(stat = "count"): Ignoring unknown parameters: `stat`

Das Hinzufügen der Anzahl pro Balken ist auch einfach, wir müssen dazu jedoch den Code etwas umschreiben. Lösung von hier: https://r-graphics.org/recipe-bar-graph-labels.

ggplot(data, aes(x=journal_type))+
  geom_bar()+
  geom_text(aes(label = ..count..), stat = "count", vjust = 1.5, colour = "white")
## Warning: The dot-dot notation (`..count..`) was deprecated in ggplot2 3.4.0.
## ℹ Please use `after_stat(count)` instead.
Balkengraphik mit für absoluten Häufigkeiten.

Abbildung 70.1: Balkengraphik mit für absoluten Häufigkeiten.

Wollen wir absolute Häufigkeit und relative Häufigkeit, gibt es verschiedene Lösungen

Variante 1 https://stackoverflow.com/questions/70258531/adding-the-text-of-count-and-percentage-by-the-group-in-a-bar-chart. Bei dieser Variante benutzen wir die dplyr Funktion count.

df_bar1 <- data %>%
  count(journal_type) %>%
  mutate(
    perc = round(proportions(n) * 100, 1),
    res = str_c(n, "(", perc, "%)"),
    journal_type = as.factor(journal_type)
    )

ggplot(df_bar1, aes(journal_type, n, fill = journal_type)) +
  geom_col() +
  geom_text(aes(label = res), vjust = -0.5)
Variante 1 für absolute und relative Häufigkeiten.

Abbildung 70.2: Variante 1 für absolute und relative Häufigkeiten.

Variante 2 In dieser variante erstellen wir zuerst ein summary Dataframe, in dem wir alle Statistiken, die wir für die Graphiken möchten, selber berechnen und auch die Labels so erstellen, wie wir sie möchten. Wir können so zum Beispiel unterschiedliche Beschriftungen erstellen, hier ein langes und ein kurzes Label.

summary_journal_type<-data %>% 
  group_by(journal_type) %>% 
  dplyr::summarise(absoluteFreq=n()) %>% 
  mutate(relativeFreq = round(absoluteFreq / sum(absoluteFreq)*100))

summary_journal_type$label = paste(summary_journal_type$journal_type,": ", summary_journal_type$absoluteFreq," (", summary_journal_type$relativeFreq,"%)", sep = "")
summary_journal_type$label_kurz = paste(summary_journal_type$absoluteFreq," (", summary_journal_type$relativeFreq,"%)", sep = "")

Hier nun die Balkengraphik:

ggplot(summary_journal_type, aes(x=journal_type, y=absoluteFreq,))+
  geom_col()+
  geom_text(aes(label = label_kurz), vjust = -0.5)+
  theme_classic()
Variante 2 für absolute und relative Häufigkeiten.

Abbildung 70.3: Variante 2 für absolute und relative Häufigkeiten.

70.2 Balkengraphik mit Fragen mit mehr als einer möglichen Antwort

Bei Fragen mit mehreren möglichen Antworten müssen wir beim Berechnen der relativen Häufigkeiten vorsichtig sein (was ist genau der Nenner?).

Am besten erstellen wir ein Datenframe im Long Format und berechnen die absoluten und relativen Häufigkeiten.

Zuerst sollten wir uns wieder einen Überblick über die Daten beschaffen. Am einfachsten geht dies mit dem tableby Befehl - aber auch hier gibt es tausend andere Möglichkeiten.

questionr::lookfor(data, "high")

pos variable label col_type values 3 highdegree_PhD — int
4 highdegree_masters — int
5 highdegree_epi — int
6 highdegree_biostats — int
7 highdegree_clinical — int
8 highdegreecountry_US — int
9 highdegreecountry_Europe — int
27 highdegree_MDorMDPhD — int
28 highdegree_clincialORmdORmdphd — int

data_highdegree<-data %>% 
  dplyr::select(randomID, contains("high")) %>% 
  mutate(across(contains("high"), factor))

table_highdegrees<-arsenal::tableby(~highdegree_PhD+highdegree_masters+highdegree_epi+highdegree_biostats+highdegree_clinical+highdegreecountry_US+highdegree_MDorMDPhD+highdegree_clincialORmdORmdphd, data=data_highdegree)
summary(table_highdegrees,cat.simplify=FALSE)
Overall (N=103)
highdegree_PhD
   0 61 (59.2%)
   1 42 (40.8%)
highdegree_masters
   0 92 (89.3%)
   1 11 (10.7%)
highdegree_epi
   0 55 (53.4%)
   1 48 (46.6%)
highdegree_biostats
   0 93 (90.3%)
   1 10 (9.7%)
highdegree_clinical
   0 66 (64.1%)
   1 37 (35.9%)
highdegreecountry_US
   N-Miss 3
   0 32 (32.0%)
   1 68 (68.0%)
highdegree_MDorMDPhD
   0 65 (63.1%)
   1 38 (36.9%)
highdegree_clincialORmdORmdphd
   0 59 (57.3%)
   1 44 (42.7%)

Wir können diese Tabelle auch noch etwas kürzer machen, indem wir nur die jeweils zweite Antwortsoption (hier die 1) ausdrucken (mit der Option cat.simplify=TRUE).

summary(table_highdegrees,cat.simplify=TRUE)
Overall (N=103)
highdegree_PhD 42 (40.8%)
highdegree_masters 11 (10.7%)
highdegree_epi 48 (46.6%)
highdegree_biostats 10 (9.7%)
highdegree_clinical 37 (35.9%)
highdegreecountry_US
   N-Miss 3
   0 32 (32.0%)
   1 68 (68.0%)
highdegree_MDorMDPhD 38 (36.9%)
highdegree_clincialORmdORmdphd 44 (42.7%)
df_highdegree_long<-data %>% 
  dplyr::select(randomID, contains("high")) %>% 
  mutate(across(contains("high"), factor)) %>% 
  pivot_longer(cols=-randomID) 
  

summary_highdegree<-df_highdegree_long %>% 
  group_by(name, value) %>% 
  dplyr::summarise(absoluteFreq=n()) %>% 
  mutate(relativeFreq = round(absoluteFreq / sum(absoluteFreq)*100)) %>% 
  dplyr::filter(value==1) %>% 
  mutate(label=paste(name,": ", absoluteFreq," (", relativeFreq,"%)", sep = "")) %>% 
  mutate(label_kurz=paste(absoluteFreq," (", relativeFreq,"%)", sep=""))
## `summarise()` has grouped output by 'name'. You can override using the
## `.groups` argument.
summary_highdegree %>% 
  mutate(Rating_Skala_1 = forcats::fct_reorder(name, desc(relativeFreq))) %>%
ggplot(aes(x=name, y=absoluteFreq,))+
  geom_col(width=0.5)+
  geom_text(aes(label = label_kurz), hjust = -0.1)+
  theme_classic()+
  scale_y_continuous(breaks=seq(0,100,10), limits=c(0,100))+ 
labs(y="Prozente", x="Item High Degree (Multiple responses possible)")+
    theme(axis.title.x = element_text(margin = margin(t = 10, r = 0, b = 5, l = 0)))+
    theme(axis.title.y = element_text(margin = margin(t = 0, r = 0, b = 0, l = 5)))+
  coord_flip()
Balkengraphik mit falschen Prozenten bei zwei Items mit fehlenden Werten (highdegreecountry_US und highdegreecountry_Europe, hier sind je drei Werte fehlend, deswegen sollten die Prozente hier auf 100 Personen gerechnet werden - ausser man würde entscheiden, die fehlenden Werte als "Nein" zu bewerten.

Abbildung 70.4: Balkengraphik mit falschen Prozenten bei zwei Items mit fehlenden Werten (highdegreecountry_US und highdegreecountry_Europe, hier sind je drei Werte fehlend, deswegen sollten die Prozente hier auf 100 Personen gerechnet werden - ausser man würde entscheiden, die fehlenden Werte als “Nein” zu bewerten.

In dieser Variante haben wir die fehlenden Werte in einem Item nicht berücksichtigt.

Wir könnten als Lösung die length2 Funktion benutzen http://www.cookbook-r.com/Manipulating_data/Summarizing_data/.

length2 <- function (x, na.rm=FALSE) {
        if (na.rm) sum(!is.na(x))
        else       length(x)
    }

Hiermit können wir dann für jede Variable die Anzahl Teilnehmenden mit nichtfehlenden Werten zählen. Der folgende Code ist nicht sehr elegant, wer einen besseren Vorschlag hat, soll ihn doch bitte unten rechts im Chat hinterlassen.

summary_highdegree<-df_highdegree_long %>% 
  group_by(name) %>% 
  mutate(total_per_name=length2(value,na.rm=TRUE)) %>% 
  group_by(name, value) %>% 
  dplyr::summarise(absoluteFreq=n(), 
                   total_per_name=max(total_per_name)) %>% 
  mutate(relativeFreq = round(absoluteFreq / total_per_name*100)) %>% 
  dplyr::filter(value==1) %>% 
  mutate(label=paste(name,": ", absoluteFreq," (", relativeFreq,"%)", sep = "")) %>% 
  mutate(label_kurz=paste(absoluteFreq," (", relativeFreq,"%)", sep=""))
## `summarise()` has grouped output by 'name'. You can override using the
## `.groups` argument.
summary_highdegree %>% 
  mutate(Rating_Skala_1 = forcats::fct_reorder(name, desc(relativeFreq))) %>%
ggplot(aes(x=name, y=absoluteFreq,))+
  geom_col(width=0.5)+
  geom_text(aes(label = label_kurz), hjust = -0.1)+
  theme_classic()+
  scale_y_continuous(breaks=seq(0,100,10), limits=c(0,100))+ 
labs(y="Prozente", x="Item High Degree (Multiple responses possible)")+
    theme(axis.title.x = element_text(margin = margin(t = 10, r = 0, b = 5, l = 0)))+
    theme(axis.title.y = element_text(margin = margin(t = 0, r = 0, b = 0, l = 5)))+
  coord_flip()
Balkengraphik mit korrekten Prozenten bei den beiden Items mit fehlenden Werten (highdegreecountry_US und highdegreecountry_Europe, hier sind je drei Werte fehlend, deswegen werden jetzt die Prozente auf 100 Personen gerechnet werden - ausser man würde entscheiden, die fehlenden Werte als "Nein" zu bewerten.

Abbildung 70.5: Balkengraphik mit korrekten Prozenten bei den beiden Items mit fehlenden Werten (highdegreecountry_US und highdegreecountry_Europe, hier sind je drei Werte fehlend, deswegen werden jetzt die Prozente auf 100 Personen gerechnet werden - ausser man würde entscheiden, die fehlenden Werte als “Nein” zu bewerten.

Manchmal sind die Daten so codiert, dass die Teilnehmenden eine 0 bekommen, egal ob sie antworten oder nicht - das ist der Fall, wenn im Fragebogen nur die Möglichkeit besteht, das Ja anzukreuzen.

Ind diesem Fall wird es schwieriger, die totale Anzahl an Personen zu bestimmen, die die Frage beantwortet haben. Wenn zum Beispiel im Online-Fragebogen eine branching-logik benutzt wurde, könnte man diese Variablen der branching logic benutzen zum Bestimmen der Teilnehmenden, die diese Frage beantworten hätten sollen. Man könnte auch berechnen, wie viele mindestens eine Option gewählt haben - dies macht allerdings nur Sinn, wenn die Frage so gestellt wurde, dass alle eine Antwortsoption wählen sollten und können.