── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr 1.1.2 ✔ readr 2.1.4
✔ forcats 1.0.0 ✔ stringr 1.5.0
✔ ggplot2 3.4.2 ✔ tibble 3.2.1
✔ lubridate 1.9.2 ✔ tidyr 1.3.0
✔ purrr 1.0.1
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag() masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
Vi skal diskutere utvelgelse av rader, ofte kjent som filtrering.
8.1 Filter
Filtre bruker vi svært ofte. De lar oss begrense mengden data vi ser på og arbeider på. Vi får filtra våre dplyr. Merk at det finnes en filter-funksjon i stats-pakka som lastes inn når vi starer R, og dette filteret blir overskrevet av dplyr/tidyverse. Om du får feilmelding når du bruker filter kan det være at du har glemt å laste inn dplyr, og at R forsøker å bruke stats’ filter().
Filtret velger ut rader ved å sjekke ut innholdet i en kolonne. Vi kan velge ut alle rader som har en viss verdi på en kolonne. La oss se på at alle menneskene i datasettet starwars.
starwars %>%filter(species =="Human")
# A tibble: 35 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Luke Sk… 172 77 blond fair blue 19 male mascu…
2 Darth V… 202 136 none white yellow 41.9 male mascu…
3 Leia Or… 150 49 brown light brown 19 fema… femin…
4 Owen La… 178 120 brown, gr… light blue 52 male mascu…
5 Beru Wh… 165 75 brown light blue 47 fema… femin…
6 Biggs D… 183 84 black light brown 24 male mascu…
7 Obi-Wan… 182 77 auburn, w… fair blue-gray 57 male mascu…
8 Anakin … 188 84 blond fair blue 41.9 male mascu…
9 Wilhuff… 180 NA auburn, g… fair blue 64 male mascu…
10 Han Solo 180 80 brown fair brown 29 male mascu…
# ℹ 25 more rows
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
Filteret velger alle radene hvor sammenlikninga vi oppgir er TRUE (sann). Mer robotisk kan vi si at den i tilfellet over velger rader hvor cella under kolonna species tilfredsstiller betingelsen “innholdet i cella er lik Human”. Dermed kan vi oppgi andre uttrykk som evalueres til enten TRUE eller FALSE. Hva med å hente ut alle som er høyere enn 170 cm?
starwars %>%filter(height >170)
# A tibble: 54 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Luke Sk… 172 77 blond fair blue 19 male mascu…
2 Darth V… 202 136 none white yellow 41.9 male mascu…
3 Owen La… 178 120 brown, gr… light blue 52 male mascu…
4 Biggs D… 183 84 black light brown 24 male mascu…
5 Obi-Wan… 182 77 auburn, w… fair blue-gray 57 male mascu…
6 Anakin … 188 84 blond fair blue 41.9 male mascu…
7 Wilhuff… 180 NA auburn, g… fair blue 64 male mascu…
8 Chewbac… 228 112 brown unknown blue 200 male mascu…
9 Han Solo 180 80 brown fair brown 29 male mascu…
10 Greedo 173 74 <NA> green black 44 male mascu…
# ℹ 44 more rows
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
Vi kan oppgi flere betingelser. Hva med alle kvinnelige mennesker?
starwars %>%filter(species =="Human"& sex =="female")
# A tibble: 9 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Leia Org… 150 49 brown light brown 19 fema… femin…
2 Beru Whi… 165 75 brown light blue 47 fema… femin…
3 Mon Moth… 150 NA auburn fair blue 48 fema… femin…
4 Shmi Sky… 163 NA black fair brown 72 fema… femin…
5 Cordé 157 NA brown light brown NA fema… femin…
6 Dormé 165 NA brown light brown NA fema… femin…
7 Jocasta … 167 NA white fair blue NA fema… femin…
8 Rey NA NA brown light hazel NA fema… femin…
9 Padmé Am… 165 45 brown light brown 46 fema… femin…
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
Ikke så veldig mange. Ikke rart Star Wars filer Bechdel-testen. Får vi med flere hvis vi ikke skiller mellom sex og gender? Altså at vi tar med dem er enten female eller feminine?
# A tibble: 9 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Leia Org… 150 49 brown light brown 19 fema… femin…
2 Beru Whi… 165 75 brown light blue 47 fema… femin…
3 Mon Moth… 150 NA auburn fair blue 48 fema… femin…
4 Shmi Sky… 163 NA black fair brown 72 fema… femin…
5 Cordé 157 NA brown light brown NA fema… femin…
6 Dormé 165 NA brown light brown NA fema… femin…
7 Jocasta … 167 NA white fair blue NA fema… femin…
8 Rey NA NA brown light hazel NA fema… femin…
9 Padmé Am… 165 45 brown light brown 46 fema… femin…
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
Nei.
Men vi fikk illustrert filteret. Vi bruker noen logiske operatorer for å kombinere ulike ledd i betingelsene våre.
&: og. Både x og y må være tilfredsstilt.
|: eller. Enten x eller y må være tilfredsstilt.
==: er lik. x må være lik y.
!=: er ikke lik. x må være ulik y.
< og >: mindre enn og større enn
<= og >=: mindre enn eller lik og større enn eller lik.
Og vi bruker () for å gruppere betingelser sammen. La oss se på hva som skjer med antall rader som blir tatt med når vi fjerner ().
# Med () rundt female og femininestarwars %>%filter(species =="Human"& (sex =="female"| gender =="feminine")) %>%nrow()
[1] 9
# Uten ()starwars %>%filter(species =="Human"& sex =="female"| gender =="feminine") %>%nrow()
[1] 17
I det første eksemplet må du være 1) menneske og 2) enten female eller feminine. I det andre eksemplet må du være 1) menneske og female eller 2) feminine. Dermed får vi med oss en del roboter og romvesener som er feminine i det andre eksemplet.
Vi bruker ofte filtre for å fjerne deler av et datasett, for eksempel dersom vi bare vil se på Trondheim. Da filtrer vi kanskje med ei kolonne som inneholder
kommunnummeret til Trondheim (5001),
det gamle kommunenummeret til Trondheim (1601),
en tekststreng med navet til byen (“Trondheim”),
en tekststreng med det gamle navnet på byen (“Trondhjem”),
en tekststreng med navnet på byen uten stor forbokstav (“Trondheim”), eller
en tekststreng med navnet på byen feilstava (“Trodnheim”).
en tekststreng med navnet på byen og noe tilleggstekst som vi ikke trenger (“Trondheim by”)
Antakelig ikke alle på en gang, men det er greit å vite hvordan man gjør en delvis match. Spesielt når vi får en liste med navn på ting som må matches med våre egne data er det typisk at de skriver navna noe annerledes enn oss. Dette er gjerne fordi det ikke egentlig er noen standard måte å skrive navna på, eller fordi navna endres. Barnehagedatasett er et typisk eksempel her.
Å matche på ulike numre handler vanligvis om å sette sammen en serie med eller betingelser via |. Vi fokuserer derfor på tekststrenger. Til dette finner vi en svært nyttig pakke som heter stringr.
8.2 Stringr
stringr har drøssevis av nyttige funksjoner for oss, og vi har så klart ikke tid å gå innom alt. En del av funksjonene baserer seg på noe som kalles regulære uttrykk (regular expressions), ofte henvist til som regex. Regex er serier med tegn som spesifiserer et spesifikt mønster. Det brukes når vi vil ha treff på flere varianter.
Under bruker jeg regex for å treffe på to skrivemåter av Trondheim. Vi bruker str_detect() for å detektere strenger i en kolonne som matcher et mønster. Mønsteret er altså Trondheim eller Trondhjem. Siden den eneste forskjellen på disse to er om vi bruker ei eller je i slutten, kan vi skrive dette som "Trondh(ei|je)m".
# Lager et fiktivt lite datasettbyer <-tibble(by =c("Trondheim", "Trondhjem", "Drammen"),valuta =c("NOK", "riksdaler", "NOK"))# Filter ved hjelp av regexbyer %>%filter(str_detect(by, "Trondh(ei|je)m"))
# A tibble: 2 × 2
by valuta
<chr> <chr>
1 Trondheim NOK
2 Trondhjem riksdaler
# En mindre effektiv måte å gjøre dette på ville værtbyer %>%filter(by =="Trondheim"| by =="Trondhjem")
# A tibble: 2 × 2
by valuta
<chr> <chr>
1 Trondheim NOK
2 Trondhjem riksdaler
Du kan spørre deg hvorfor det andre eksemplet er mindre effektivt når det bare koster oss noen få ekstra tegn. Fordi jeg må gjenta meg sjøl når jeg skriver by == og Trondh. Akkurat i dette tilfellet er det ikke særlig alvorlig. Men når vi utvider og får større datasett og mer avanserte søkemønstre vil det begynne å gjøre seg gjeldende. En ting er at vi sparer tid på skrive mindre. En annen ting er at det blir lettere å gjøre endringer seinere. Fordi vi ikke må endre en serie med eller-betingelser, men kun den ene regex-en.
Alle funksjonene i stringr starter med str_, som gjør dem lett å søke opp. Nyttig, for det er mange av dem! De jeg bruker oftest er
str_sub() for å hente ut en del av en streng
str_detect() i kombinasjon med filter() for å finne en delvis match i en kolonne
str_replace() for å erstatte del av en streng med noe annet
str_to_lower() og str_to_upper() for å fjerne feilkilder når jeg søker. Spesielt den første er nyttig. Hvis jeg skal matche på navn vil jeg unngå at jeg får ikke-match bare fordi noen navn har store bokstaver enkelte steder mens andre ikke. Jeg vil at “Byåsen barnehage” skal matche med Byåsen Barnehage. Merk at det finnes varianter av disse i base R også.
La oss se noen eksempler på bruk av disse
# Hent ut de fire første bokstavene av en kolonnebyer %>%mutate(by4 =str_sub(by, 1, 4))
# A tibble: 3 × 3
by valuta by4
<chr> <chr> <chr>
1 Trondheim NOK Tron
2 Trondhjem riksdaler Tron
3 Drammen NOK Dram
# Bytt ut deler av en strengbyer %>%mutate(nytt_bynavn =str_replace(by, "Trond", "Trønder"))
# A tibble: 3 × 3
by valuta nytt_bynavn
<chr> <chr> <chr>
1 Trondheim NOK Trønderheim
2 Trondhjem riksdaler Trønderhjem
3 Drammen NOK Drammen
# Gjøre om en kolonne til små bokstaver. byer %>%mutate(valuta_lower =str_to_lower(valuta))
# A tibble: 3 × 3
by valuta valuta_lower
<chr> <chr> <chr>
1 Trondheim NOK nok
2 Trondhjem riksdaler riksdaler
3 Drammen NOK nok
stringr har mange muligheter i seg. Spesielt bruken av regex er svært nyttig, som nevnt over. Men læringskurva er bratt. Og særlig det å søke etter tall i en tekststreng. Noen nyttige funksjoner her er å kombinere tegntype og kvantitet. Sjekk ut side to av stringr-jukselappen til Posit.
Her lager jeg et rart datasett for å vise hvordan vi kan bruke disse regex-kommandoene. Datasettet blir laga ved å sette sammen tilfeldige serier med tall og bokstaver som vi seinere kan søke på. Her bruker jeg set.seed() for å sørge for at de påfølgende randomiserte prosessene blir like hver gang de kjøres. Slik at du og jeg ser de samme talla hver gang.
set.seed(123)# En funksjon som lager en serie av siffer og bokstaver av ulik lengde.lag_streng <-function() { tall <-seq(1:10) %>%as.character() bokstaver <- letters[1:10] tall_bokstaver <-c(tall, bokstaver)paste0(sample(tall_bokstaver, size =runif(1, 3, 5), replace =TRUE), collapse ="")}set.seed(123)# Setter det sammen i et datasett. utvalg <-tibble(id =seq(1:200), name =replicate(200, lag_streng()))# Slik ser det ut. utvalg
Dette mønstret ser overveldende ut, så la oss pakke det ut: "[[:alpha:]]{2}[[:digit:]]{2}"
[[:alpha:]] treffer alle bokstaver.
[[:digit:]] treffer alle tall.
{2} betyr nøyaktig to forekomster av det som kom før meg. Vi bruker den både for bokstaver og tall. Alternativet ville vært å skrevet f.eks. [[:alpha:]][[:alpha:]]
Det er verdt å tenke litt på tegnkoding igjen. Hvordan lagres informasjon om tall og bokstaver på pc-en? Hvordan behandles tall og bokstaver (og symboler) annerledes av programmeringsspråk som R? En tallserie vil f.eks. sorteres annerledes avhengig av om den er koda som numerisk eller streng.
# En vektor som inneholder en serie fra 1 til 24 i tilfeldig rekkefølgetall <-sample(c(1:24), 24)# Vi sortere den først når den er numerisk og deretter når den er en streng.tall %>%sort()
I neste kodeblokk lager jeg to datasett. Det første er likt det vi hadde i sted, foruten at jeg kun henter ut de første ti radene. Det andre datasettet er likt dette, foruten at jeg har bedt om kun ett siffer (digit) på slutten. For å vise tydeligere hva som skjer binder jeg de to radene sammen.
Legg merke til kolonna helt til høyre. Vi ba om kun ett siffer, likevel får vi “7be10” og “jd38”. Hvorfor? Fordi de inneholder, nettopp “ett siffer”. Dette sifferet er tilfeldigvis etterfulgt av et annet siffer, men det sa vi ikke spesifikt at vi skulle unngå. Her gjorde jeg en antakelse som viste seg å ikke stemme. Det var en antakelse jeg ikke var helt bevisst at jeg gjorde, og det var at koden min skulle vise meg rader som inneholder to bokstaver og ett, og kun ett, ikke to eller flere, siffer.
Vi ser det samme skjer med den første kolonna også: her får vi treff på to bokstaver etterfulgt av tre og fire siffer. Hvis vi skulle fiksa koden til å treffe på “to bokstaver etterfulgt av kun to og ikke flere siffer” kunne vi endra det slik:
Det jeg håper på å få fram her er at vi må stoppe opp innimellom og teste våre egne antakelser og arbeid.
8.3 Select
Vi har brukt select() før, se sec-select. Den er nyttig å ta opp igjen her. Med select() kan vi velge ut kolonner og med filter() kan vi velge ut rader. Dermed er det ofte nyttig å bruke begge sammen. Jeg bruker dem spesielt mye når jeg gjør en første gjennomgang av et skript. Da velger jeg ut kun de radene og kolonnene som jeg er interessert i og arbeider på dem. Dette gjør det lettere å se om koden min funker, uten å måtte se for mye urelevant. Når jeg veit at skriptet funker, kan jeg gå tilbake og fjerne select() og filter() slik at hele datasettet kjøres gjennom skriptet.
Det er nyttig å vite at vi kan bruke noen liknende streng-teknikker på select() som vi brukte på filter(). Vi henter inn et nytt, simulert datasett hvor den del av kolonnene våre har navn på samme form.
Her har vi kun tre av hver kolonne, men se for dere at vi hadde hatt tredve. Da ville det vært nyttig å slippe å skrive inn navnet på hver enkelt.
La oss si at vi vil ha tak i kun de faktiske befolkningstalla, altså de kolonnene som har “faktisk” i navnet. Vi kan bruke contains() eller matches() inni select().
Hva er forskjellen på dem? matches() lar oss bruke en regex. Si at vi vil ha kolonner med “faktisk” i navnet, etterfulgt av en eller flere siffer, og som slutter på 5.
Jeg bruker ofte select() til å endre rekkefølgen på kolonner. Ofte vil jeg ha den kolonna jeg nettopp lagde fremst. Da er det nyttig å huske på noen av triksa fra tidy evaluation: everything(). Den lar meg slippe å skrive opp alle kolonnene i datasettet: