Sesjon 10: Mer tegning: ggplot2
R-workshop 10. oktober, OsloMet
Nå skal vi tegne mer, men denne gangen skal vi bli kjent med ggplot2 pakken. R hadde allerede etablert seg som en god programvare for visuell datapresentsajon, men denne pakken har bidratt til å gjøre R uovertruffen ved å tilgjengligjøre det grafiske aspektet.
ggplot2
fungerer godt i samband med datatilrettelegging
med dplyr
. Den har – irriterende nok – sine egne piper.
Jevnt over, krever ggplot2 flere argumenter og funksjoner for å gi enkle
plot, men mulighetene for tilpasninger er også veldig store. Dermed er
ggplot-grafikkene mine produsert med relativt mange kodelinjer. Jeg
bruker dem i større grad når jeg skal presentere for andre, mens jeg tyr
til kjappere løsninger når jeg bare skal ta en “titt”.
Arbeidsflyten min i ggplot2 er som følger:
- jeg etablerer et eget lite datasett med informasjonen jeg ønsker å bruke i grafikken. Det betyr at jeg må tenke gjennom hvilken informasjon jeg ønsker og trenger for visualiseringen. Som oftest jobber jeg iterativt. Det vil si at jeg starter med et enkelt utgangspunkt, så går jeg heller tilbake og legger til nye elementer etter hvert.
- pipen leses kronologisk: Jeg etablerer først et ark, så legger jeg på elementer
- de visuelle tilpassningene (“theme”) kan baseres på en mal. Da er alt presisert. Deretter kan kan jeg “tweake” det jeg ønsker.
Google er som vanlig din venn.
library(ggplot2); library(dplyr)
Vi laster inn ESS-dataene fra forrige sesjon (med mindre vi fortsatt har dem).
load(
url("https://siljehermansen.github.io/teaching/oslomet/kap7.rda")
)<- kap7 df
Uforsk hele datasettet
Jeg sa nettopp at jeg etablerer et eget “plotdatasett” for ggplot. Før vi gjør det, vil jeg noen ganger plotte selve analysedataene mine likevel.
Spredningsdiagram
Hovedfunksjonen ggplot()
etablerer plottet, men ingen
plotelementer. Om du skal bruke de samme dataene for alle de grafiske
elementene, så kan du presisere dataobjektet allerede i denne
funksjonen. Når du gjør det, presiserer du ikke datasettet ditt flere
ganger i pipen, så du sparer tid. Fra da av, kan du referere rett til
variablene som ligger i det.
Du binder alle elementer sammen med en pipe ved hjelp av “+”. Her er
det fort å glemme seg, slik at man heller bruker
%>%
-pipen fra tidyverse. Du er ikke den eneste som vil
gjøre den feilen, så meldingen du får er overraskende intuitiv:
Did you use %>% instead of +?
.
ggplot(data = df)
Hovedelementene i grafikken legges inn ved hjelp av en serie med
geom_...()
funksjoner. De presiserer “geometrien”, altså
hva som skal legges inn (informasjonen fra variablene) og hvor dette
skal ligge (koordinatene) .
Inni hver av dem finner du estetikkfunksjonen, aes()
.
Der oppgir du x/y-koordinatene som kommer fra variablene dine.
Her lager jeg et spredningsdiagram for sammenhengen mellom
innvandringsskepsis og inntekt. Det betyr at jeg ønsker å tegne punkter
(geom_point()
).
ggplot(df) +
geom_point(aes(y = Skepsis,
x = Inntekt))
Inntektsvariabelen består kun av heltall. Dermed overskriver punktene hverandre, og vi får en dårlig idé om hvor mange enheter som finnes innen de ulike verdikombinasjonene. Vi kan løse dette på to måter: Ved å gjøre punktene delvis gjennomsiktige, eller ved å flytte dem litt fra hverandre.
Gjennomsiktig farge Jeg kan oppgi at jeg ønsker
gjennomsiktige farger. Dette gjør jeg med argumentet
alpha=
. Verdien jeg oppgir rangerer fra 0 (helt
gjennomsiktig/usynlig) til 1 (ikke-transparent).
ggplot(df) +
geom_point(aes(y = Skepsis,
x = Inntekt),
#Gjennomsiktighet: for alle observasjoner
alpha = 0.2)
Notér at når vi skal tilrettelegge punktene uten at tilretteleggingen
er avhengig av informasjon fra dataene, så foregår dette
utenfor aes()
men innenfor
geom_...()
funksjonen.
Riste data Et annet alternative er å “riste” datapunktene litt. På engelsk kaller vi dette “jitter”. Da legger R til en tilfeldig variasjon i punktenes koordinater. Selve datapunktene blir mer upresise, men poenget her er ikke en presis analyse, men å presentere sammenhenger i dataene slik at det menneskelige øyet får en intuitiv forståelse av hva som foregår.
ggplot(df) +
geom_jitter(aes(y = Skepsis,
x = Inntekt),
#Riste data horisontalt, men ikke vertikalt
height = 0, width = 0.3)
Tilleggsargumentene width=
og height=
presiserer hvor mye punktene skal ristes horisontalt og vertikalt,
respektivt. Her sier jeg at jeg ønsker presise data langs y-aksen (“i
høyden”), men lar punktene variere tilfeldig med 0.3 enheter langs
x-aksen (“i bredden”).
Trivariate grafikker: Gruppér dataene.
Jeg kan enkelt legge til flere grupperinger. Her fargelegger jeg punktene etter verdien på en tredje variabel: Den variabelen kan være kontinuerlig eller kategorisk.
Kontinuerlig gruppering “Subjektiv inntekt” rapporterer hvor rik respondenten føler seg i fire kategorier fra 0 (“lav tilfredshet”) til 3 (“høy tilfredshet”).
ggplot(df) +
geom_point(aes(y = Skepsis,
x = Inntekt,
#Gruppering
color = SubjektivInntekt))
Nå får jeg en “varmeskala” for respondentens oppfattelse av egen inntekt. ggplot gir oss også automatisk en tegnforklaring.
Her ser vi at de fleste respondentene som rapporterer lav tilfredshet (mørk farge) befinner seg til venstre i plottet. De har lav (objektiv) inntekt.
Kategorisk gruppering Skal jeg ha en kategorisk
gruppering, må jeg fôre funksjonen inn med en kategorisk variabel
(“character” eller “factor”). Det beste er å gjøre dette i plotdataene,
ikke i ggplot()-funksjonen. Akkurat her, fraviker jeg fra denne regelen
med (as.factor()
)
ggplot(df) +
geom_point(aes(y = Skepsis,
x = Inntekt,
#Gruppering
color = as.factor(SubjektivInntekt)))
Én løsning forhindrer ikke en annen. Jeg kan selvsagt kombinere alle disse løsningene fram til jeg får et plot som forteller historien jeg ønsker å formidle.
ggplot(df) +
geom_jitter(aes(y = Skepsis,
x = Inntekt,
#Kontinuerlig tredje/modererende variabel
color = SubjektivInntekt),
#Riste data horisontalt og vertikalt
height = 0.1, width = 0.4,
#Gjennomsiktig farge
alpha = 0.7)
Bivariate regresjonslinjer
Jeg kan også vise sammenhengen mellom to variabler ved hjelp av regresjonslinjer. ggplot rapporterer ikke regresjonskoeffisientene, men illustrerer i stedet regresjonslinjen og usikkerheten rundt den. Dette er spesielt relevant hvis minst en av variablene (x-variabelen) er kontinuerlig.
Nå kan vi velge mellom lokal regresjon og vanlig lineær regresjon. Hvilken vi velger, avhenger av hva vi vil oppnå.
Lokal regresjon
Lokal regresjon er – i mine øyne – dronningen av alle ggplot-funksjoner. Jeg bruker den hele tiden når jeg utforsker dataene mine. Den tegner en “myk” linje (lokalt, glidene gjennomsnitt over de ulike x-verdiene). Helt perfekt for tidstrender eller for å utforske ikke-lineære sammenhenger, for eksempel.
Funksjonen som gir oss bivariate regresjonslinjer er
geom_smooth()
. Default-instillingen er lokal regresjon.
ggplot(df) +
geom_smooth(aes(y = Skepsis,
x = Alder))
## `geom_smooth()` using method = 'gam' and formula 'y ~ s(x, bs = "cs")'
Her illustrerer jeg sammenhengen mellom alder og innvandringsskepsis. Figuren demonstrerer den største fordelen med lokal regresjon: Det finnes ingen lineær sammenheng mellom alder og innvandringsskepsis. I stedet ser det ut til at yngre og eldre respondenter er mer skeptiske.
R tilpasser “vinduet” automatisk for oss, men vi kan velge størrelsen
selv. Da anvender vi tilleggsargumentet span =
. Hvor stort
spenn av variabelen skal brukes for det lokale gjennomsnittet? Lave
verdier gir en mer krøllete strek, høye verdier gir mer
generalisering.
ggplot(df) +
geom_smooth(aes(y = Skepsis,
x = Alder),
span = 20)
## `geom_smooth()` using method = 'gam' and formula 'y ~ s(x, bs = "cs")'
Gruppert: Er sammenhengen lik innenfor ulike kategorier?
ggplot(df) +
geom_smooth(aes(y = Skepsis,
x = Inntekt,
color = as.factor(SubjektivInntekt)))
## `geom_smooth()` using method = 'loess' and formula 'y ~ x'
ggplot(df) +
geom_smooth(aes(y = Skepsis,
x = Inntekt,
color = as.factor(Kvinne)))
## `geom_smooth()` using method = 'loess' and formula 'y ~ x'
ggplot(df) +
geom_smooth(aes(y = Skepsis,
x = Inntekt,
color = as.factor(Kvinne)))
## `geom_smooth()` using method = 'loess' and formula 'y ~ x'
Vil du bytte ut spaghettien med rette linjer? Be om en lineær modell.
ggplot(df) +
geom_smooth(aes(y = Skepsis,
x = Inntekt,
color = as.factor(SubjektivInntekt)),
method = "lm")
## `geom_smooth()` using formula 'y ~ x'
Her ser vi klart at sammenhengen mellom innvandringsskepsis og inntekt først og fremst er tilstede for respondenter som selv føler at de mangler penger (kategoriene 0 og 1).
Boksplot
ggplot(df %>%
filter(!is.na(SubjektivInntekt))) +
geom_boxplot(aes(y = Skepsis,
x = as.factor(SubjektivInntekt),
fill = SubjektivInntekt),
notch = T)
## notch went outside hinges. Try setting notch=FALSE.
Fiolinplot
ggplot(df %>%
filter(!is.na(SubjektivInntekt))) +
geom_violin(aes(y = Skepsis,
x = as.factor(SubjektivInntekt)))
Tetthetsgrafikker
library(ggridges)
ggplot(df %>%
filter(!is.na(SubjektivInntekt))) +
geom_density_ridges(aes(x = Skepsis,
y = as.factor(SubjektivInntekt)),
fill = "white") +
coord_flip()
## Picking joint bandwidth of 0.558
Søylediagram/histogram
Søylediagrammet og histogrammet tar minst ett argument: Vi oppgir hvorvidt høyden på søylene skal defineres langs y-aksen (vertikale søyler) ller x-aksen (horisontale søyler). ggplot teller antall observasjoner for oss.
ggplot(df) +
geom_bar(aes(x = Skepsis))
Histogrammet følger samme regler.
ggplot(df) +
geom_histogram(aes(x = Skepsis))
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
Vi kan lage et tetthetsdiagram med samme logikk.
ggplot(df) +
geom_density(aes(x = Skepsis))
ggplot(df %>% filter(!is.na(SubjektivInntekt))) +
geom_density(aes(x = Skepsis,
color = as.factor(Utdanning),
fill = as.factor(Utdanning)),
position = "stack",
alpha = 0.5)
library(ggridges)
ggplot(df %>% filter(!is.na(SubjektivInntekt))) +
geom_density_ridges(aes(x = Skepsis,
y = as.factor(Utdanning),
color = as.factor(Utdanning),
fill = as.factor(Utdanning)),
alpha = 0.7) +
theme_minimal()
## Picking joint bandwidth of 0.566
Flere plot:
ggplot(df %>%
filter(!is.na(SubjektivInntekt))) +
geom_smooth(aes(x = Inntekt,
y = Skepsis,
group = as.factor(SubjektivInntekt)),
method = "lm") +
facet_wrap(facets = vars(SubjektivInntekt))
## `geom_smooth()` using formula 'y ~ x'
Lag et grafisk datasett først
La oss følge en alternativ arbeidsflyt og etablere et eget lite datasett for plotting. Her er jeg interessert i innvandringsskepsis blant respondenter med ulik oppfatning av egen inntekt.
<- df %>%
dfp #Filtrer ut NA
filter(!is.na(SubjektivInntekt)) %>%
#Grupper
group_by(SubjektivInntekt) %>%
#Gruppegjennomsnitt
summarize(Innvandringsskepsis = mean(Skepsis, na.rm = T))
#Lag en ny kategorisk variabel med selve survey spørsmålet
$`Hvor godt lever du på inntekten din?` <- dfp$SubjektivInntekt %>%
dfp
as.factor#Omdefiner kategoriene til intuitive svar
levels(dfp$`Hvor godt lever du på inntekten din?`) <- c("Svært vanskelig",
"Vanskelig",
"Endene møtes",
"Komfortabelt")
Her kan du be
ggplot(dfp) +
#Klassisk definisjon av både x og y koordinater
geom_bar(aes(y = Innvandringsskepsis,
x = `Hvor godt lever du på inntekten din?`),
#Bruker tallene du oppgir som søylehøyde uten å telle selv.
stat = "identity")
<- df %>%
dfp #Filtrer ut NA
filter(!is.na(SubjektivInntekt) &
!is.na(Prekaritet)) %>%
#Grupper
group_by(Prekaritet, SubjektivInntekt) %>%
#Gruppegjennomsnitt
summarize(Innvandringsskepsis = mean(Skepsis, na.rm = T))
## `summarise()` has grouped output by 'Prekaritet'. You can override using the
## `.groups` argument.
#Omkod to modrererende variabler
#Lag en ny kategorisk variabel med selve survey spørsmålet
$`Hvor godt lever du på inntekten din?` <- dfp$SubjektivInntekt %>%
dfp
as.factor#Omdefiner kategoriene til intuitive svar
levels(dfp$`Hvor godt lever du på inntekten din?`) <- c("Svært vanskelig",
"Vanskelig",
"Endene møtes",
"Komfortabelt")
$Arbeidssituasjon = ifelse(dfp$Prekaritet == 1,
dfp"Præker", "Trygg")
dfp
Her kan du be
<-
p ggplot(dfp) +
#Klassisk definisjon av både x og y koordinater
geom_bar(aes(y = Innvandringsskepsis,
x = `Hvor godt lever du på inntekten din?`,
#Fyll ut med en farge ut fra grupperingen
fill = Arbeidssituasjon),
#Legg soylene ved siden av hverandre
position = "dodge",
#Bruker tallene du oppgir som søylehøyde uten å telle selv.
stat = "identity")
Merk deg hvordan jeg har tatt vare på plottet mitt ved å lagre det i
et objekt: p <- ggplot()
. For å se plottet ber jeg om
det.
p
Det er nyttig å lagre objektet på denne måten fordi jeg i det videre skal endre på R-objektet mitt. Det kan jeg gjøre via piper.
Tilrettelegging.
Når du har lagt inn de grafiske elementene med dataene dine, kan du bruke uendelig tid på å finpusse resten av plottet.
Fortell R hvilket språk du snakker (og hvor du er)
R er ikke skrevet av nordmenn, så de norske bokstavene kan av og til skape problemer. Vi må forholde oss aktivt til tegnsettingsvalg i to settinger: Når vi henter inn data og når vi lagrer data; inkludert i grafikker.
Datamaskiner leser og gir fra seg informasjon som en serie med 0-er
og 1-ere. For å lese og skrive tekst, opererer vi med tegnsetting
(“encoding”); en oversettelse av binære koder til alfabetet vi kjenner.
Tradisjonelt har hvert land og språk hatt hvert sitt tegnsettingssystem.
Det er tungvint, og idag følger det meste av tekst som utveksles på
internett, “utf-8”-tegnsetting. Der finnes de aller fleste tegn som vi
har lært å lese. Sys.setlocale()
forteller R hvor du er i
verden: Her forteller jeg at jeg ønsker norsk språk med utf-8 tegn.
Sys.setlocale(category = "LC_ALL",
locale = "nb_NO.UTF-8")
Definer farger
Du kan velge farger selv. Dette gjøres på mange måter. Om du har linket data med fargevalg, kan det være lurt å definere en “palett”.
Selve teksten (“Prekær” og “Trykk”) er en del av dataene jeg oppga, så disse befinner seg i variabelen “dfp$Arbeidssituasjon”. Jeg kan endre dem i ggplot hvis jeg vil. Dette gjør jeg når jeg definerer fargene.
<-
p +
p #Definer fargene
scale_color_manual(
#Hva er fargene?
values = c("purple", "magenta"),
#Fargelegg hele søylen
aesthetics = "fill",
#Hva er tittelen for tegnforklaringen?
name = "Tilknytning til arbeidslivet",
#Hva er kategorinavnene?
labels = c("Midlertidig/uten arbeid",
"Fast arbeid")
) p
Bakgrunnen
Du kan definere ulike maler for de estetike delene av plottet med
theme_...()
.
<-
p +
p #Definer et tema
theme_minimal()
p
Disse kan du så endre på, hvis du vil.
<-
p +
p #Endre på minimal-temaet etter å ha definert det
theme(
#Flytt tegnforklaringen ned til under plottet
legend.position = "bottom",
#Fet skrift for tittelen i tegnforklaringen
legend.title = element_text(face = "bold"),
#Kursiv for akseverdiene
axis.text = element_text(face = "italic"),
#Fjern støttelinjene
panel.grid = element_blank())
p
Funksjonene element_...()
brukes inni
theme()
funksjonen. De oppgir alt unntatt
datainnholdet/informasjonen til et element: Skal det være et blankt
element element_blank()
? Spesifikk skrift?
element_text()
… osv.
Eksempelvis, kan jeg fortelle at teksten som brukes for aksenavnene
skal vises som kursiv element_text(face = "italic")
.