Tilbage til menu

Introduktion

R et et script-baseret statistikprogram, som bruges til datahåndtering, dataanalyse og visualisering. Script-baserede programmer fungerer ved hjælp af kommadoer eller koder, som fortæller programmet hvad det skal gøre - hvorefter programmet returnerer et output.

Vi kan fx udregne 5 + 5 ved hjælp af denne kommando:

5 + 5
## [1] 10

eller denne kommando:

sum(5,5)
## [1] 10

For at programmet gør det vi ønsker, at det skal gøre, er vi derfor nødt til at lære, hvordan vi skriver kommandoerne rigtigt.

I dette dokument vil jeg gennemgå de basale ‘regler’ for hvordan vi skriver kommandor, som man er nødt til at kunne for at komme i gang.

R er et utroligt alsidigt program - og det vil derfor ikke være muligt at gennemgå alt hvad det kan. Der findes yderligere forskellige ‘dialekter’ (måder at skrive kode på, fx TidyVerse eller DataTables), hvor der er lidt forskellige regler og metoder. Her vil jeg kun gennemgå de regler, der er indbygget i grundprogrammet (kaldes også {baseR}).

Introduktionen er tiltænkt personale med en grundlæggende forståelse for sundhedsfaglig statistik og forskningsdata, men ikke nødvendigvis nogen erfaring med programmering eller brug af andre scriptbaserede statistikprogrammer. Men hvis man har en smule erfaring med at arbejde i programmer som SAS eller Stata vil dette også kunne være en hurtig guide til begreberne og syntaxen i R.

R/RStudio

Selv programmet R består kun af en prompt, hvor man kan skrive en kommando og få et output.

Dette er ikke særligt brugervenligt og vi bruger derfor brugergrænsefladen RStudio til at køre programmet. RStudio er et integreret miljø, der består af fire vinduer/vignettes, hvor standard vinduerne er Console, Script, Environment og en menu med forskellige faner (fx Files, Packages og Help).

  • Console er selve ‘regnemaskinen’, hvor koden eksekveres og hvor outputtet også vises.
  • Script vinduet er et dokumentvindue, hvor man kan skrive kode, som herefter kan eksekveres. Man kan arbejde i flere formater i dette vindue, hvor script er standard (script er ren kode, hvor outputtet vises i console). Er mere brugervenligt format er Markdown, som både kan indeholde tekst og kode.
  • Global Environment viser alle de dataelementer, som er tilgængelige. Det giver derfor et hurtigt overblik over ens data.
  • Vinduet nederst til højre gør det nemt at navigerer rundt i filer og pakker.

VIGTIGT Hvis man skal bruge RStudio er man nødt til at installere både R og RStudio. Det er vigtigt, at man først installerer R og herefter RStudio, da RStudio skal bruge nogle filer fra R for at fungerer.

RMarkdown

Der er mulighed for at arbejde i forskellige fil-formater i RStudio, men til de fleste opgaver er det en fordel at arbejde i den dokumenttype, der hedder RMarkdown.

Opret et markdown dokument ved at gå til Files > New File > R Markdown

I RMarkdown formattet kan man både skrive tekst og kode i særlige kodeblokke. Når man eksekverer koden i en kodeblok vises outputtet nedenunder. Derved har man alt sit arbejde samlet i ét vindue, hvilket giver et lettere overblik.

RMarkdown giver også mulighed for at man kan eksportere indholdet til andre dokumenttyper, fx HTML eller word, hvor teksten formateres og koden eksekveres. Det vil sige at man kan lave alle sine analyser og skrive brødtekst i ét dokument, og herefter eksportere det hele som en samlet rapport. (Hele denne guide er fx skrevet i RMarkdown).

Du kan læse mere om Markdown her: https://rmarkdown.rstudio.com/lesson-15.HTML

I 2022 fik RStudio et nyt fil-format, Quarto, som er en videreudvikling af RMarkdown. De to formatter ligner hinanden meget - og for de fleste brugere vil det ikke gøre nogen forskel hvilket man bruger. De primære fordele ved Quarto er, at det bedre understøtter andre kodesprog (fx Python) og at det er kompatibelt med andre programmer (fx Jupyter og VS Code)

Hvordan bruger man R?

Før vi kan begynde at analysere vores data er der nogle grundlæggende ting vi skal kunne. Der er nogle regler for hvordan vi fortæller R hvad det skal gør.

Regler for mellemrum og linjeskift

Som udgangspunkt ignorer R mellemrum - med den undtagelse at man IKKE må dele navne på elementer eller funktioner.

Mellemrum kan bruges til at gøre ens kode mere læsevenlig, når man selv eller andre skal finde rund i den. Men det har ingen indflydelse på hvordan koden eksekveres.

Begge disse er korrekt

# Uden mellemrum
seq(from=10,to=100,by=10)
##  [1]  10  20  30  40  50  60  70  80  90 100
# Med mellemrum
seq ( from = 10, to = 100, by = 10)
##  [1]  10  20  30  40  50  60  70  80  90 100

Dette giver tilgengæld en fejl, fordi funktions navn er delt:

se q(from=10,to=100,by=10)

R er også rimeligt fleksibel overfor linjeskift, med nogle få undtagelser.

Fx skal linjeskift helst være efter tegn, da det nogle gange kan påvirke eksekveringen, hvis en ny linje starter med et tegn. Det er ikke altid til at forudsige hvornår det påvirker koden, så gør det til en vane at lave linjeskiftet efter et tegn.

# Dette er helt fint
seq(
  from=10,
  to=100,
  by=10)
##  [1]  10  20  30  40  50  60  70  80  90 100

R har ingen særlige tegn til at afslutte kommandor, som man kender det fra andre programmeringssprog (fx semikolon i SAS eller JavaScript). Det man skal være mest opmærksom på er at få afsluttet parenteser korrekt.

Kommentarer

Kommentarer kan bruges til at forklare R-kode - hvilket kan gøre koden mere overskuelig og lettere at finde rundt i.

Dels kan det hjælpe en selv med at navigere i ens egen kode - men det kan også gøre koden lettere forstålig for andre, hvilket er vigtigt når man samarbejder med andre.

Al kode som kommer efter et # vil blive ignoreret og tegnet bruges derfor til at skrive kommentarer:

# Alt hvad der skrives efter et #-tegn bliver ignoreret
# Det bruges til at skrive kommentarer til ens kode, fx for at forklare, hvad 
# et stykke kode gør - så både en selv og andre kan gennemskue koden i fremtiden

Data og variable

Variable er elementer, der bruges til at opbevare data i R.

Variable defineres ved at tildele dem en værdi, hvilket gøres med en pil <-, fx:

myVariable <- "Hello World" 

# Lighedstegn kan også bruges, men anbefales ikke, da de kan give problemer
# i nogle typer af funktioner
myVariable = "Hello World"

og variablen kan herefter kaldes ved at skrive dens navn

myVariable
## [1] "Hello World"

R har også en print() funktion - som man kender det fra andre programmeringssprog - men den er i princippet unødvendig:

print(myVariable)
## [1] "Hello World"

Regler for input

Der er nogle få regler for input, når man definerer variable:

  • Variablen skal have et gyldigt navn (se længere nede)
  • Tekst variable skal omsluttes af enkelt (’) eller dobbelt (“) citationstegn
  • Heltal indtastes som de er
  • Der kan kun bruges punktum som decimalseperator
  • Konstanterne TRUE og FALSE bruges kun til logiske variable (Det samme gælder T og F, som bruges som short-hand for TRUE og FALSE)
  • Det er vigtigt at skelne mellem NULL og NA, da de håndteres meget forskelligt: NULL bruges til at skabe et udefineret element (Har ingen værdi), mens NA bruges til at angive et element med manglende værdi.
tekstVar <- "Tekst"
heltalVar <- 42
decimalVar <- 3.14195
logiskVar <- TRUE
nullVar <- NULL
naVar <- NA

Scope af data

Begrebet scope handler om tilgængeligheden af data - altså om data er tilgængeligt for alle funktioner (globalt score) eller kun indenfor et afgrænset område (lokalt score).

Data, som har et globalt score, vil kunne ses i det vindue i R-Studio, der hedder 'Environment' (som standard i øverste højre hjørne). Alle data, som fremgår af Global environment vil være tilgængeligt for alle funktioner i R.

Noget data, som håndteres inde i funktioner, vil kun have et lokalt score indenfor funktionen, fordi det kun skal bruges midlertidigt. Det vil derfor ikke være tilgængeligt for andre funktioner - med mindre man flytter det til det globale miljø.

Fordelen ved at holde noget data lokalt, frem for globalt, er bl.a. at det kræver mere hukommelse at fastholde data globalt, samt at det globale miljø hurtigt bliver uoverskueligt, hvis man hele tiden fylder flere elementer derover. Brugen af lokale variable mindsker også risikoen for at man ved en fejl kommer til at ændre eller slette dele af det originale dataset (fx at man kommer til at overskrive en data.frame med et subset og derved sletter dele af datasettet).

Fjernelse af variable

Hvis man har brug for at slette en variabel, kan man bruge remove funktionen {rm()}:

rm(minVariabel)

Dette bruges fx til at rydde op i det globale miljø, så man ikke mister overblikket.

Datatyper

Data kan have forskellige typer og det er vigtigt at data har den rigtige type, da det har betydning for hvordan data håndteres. Mange af de fejl man får i R handler om, at data har det forkerte format i forhold til den funktion man bruger.

De basale typer er:

  • numeric - alle tal
  • integer - heltal
  • complex - komplekse tal
  • character - tekst
  • logical - boolske værdier (TRUE/FALSE)

I de fleste tilfælde kan R godt genkende den korrekte type, men ellers kan man ‘tvinge’ et format igennem:

mitNummer <- as.numeric(40)

Andre vigtige formater

Factor

Factors er kategoriske variable med et defineret antal niveauer og eventuelt labels

minFactor <- sample(1:3, 20, replace = TRUE) # Tilfældige tal 1, 2, 3
minFactor
##  [1] 3 2 3 1 2 1 1 1 2 2 3 1 2 3 1 3 2 3 2 1
# Formatering til factor med tre definerede niveauer
minFactor <- factor(minFactor, labels = c("Ryger","Tidligere ryger","Aldrig ryger"))
minFactor
##  [1] Aldrig ryger    Tidligere ryger Aldrig ryger    Ryger          
##  [5] Tidligere ryger Ryger           Ryger           Ryger          
##  [9] Tidligere ryger Tidligere ryger Aldrig ryger    Ryger          
## [13] Tidligere ryger Aldrig ryger    Ryger           Aldrig ryger   
## [17] Tidligere ryger Aldrig ryger    Tidligere ryger Ryger          
## Levels: Ryger Tidligere ryger Aldrig ryger

Date/time

Tidsdata er generelt lidt kompliceret, da det kan formateres på forskellige måder og har forskellige enheder. De mest anvendte standarder er POSIXct og POSIXlt, som begge opbevarer tid som numeriske enheder siden udgangspunktet (1. januar 1970).

Hvis man indlæser data, hvor en dato fx står som “08-10-20”, vil R i udgangspunktet opfatte det som tekst - og derfor lave den til et character format. Man er derfor nødt til at fortælle R, at dette er en dato.

Derudover vil “08-10-20” både kunne læses som 10. august OG 8. oktober, samt 1920 eller 2020; afhængigt af formatet - og man er derfor nødt til at fortælle R, hvordan man ønsker formateringen:

dato <- "08-10-20"
dato <- as.POSIXlt(dato, format="%d-%m-%y")
dato
## [1] "2020-10-08 CEST"

Datastrukturer

Da vi sjældent arbejder med data som enkeltværdier, har vi brug for nogle metoder til at organisere samlinger af flere værdier. Dette gør vi ved hjælp af datastrukturer, som er forskellige former for samlinger af data. De forskellige datastrukturer har forskellige egenskaber, fx forskellige dimensioner eller håndteringer af datatyper.

Vektor

Den simpleste datastruktur er en vektor, som kan indeholde en række data af samme datatype i en dimension.

# Vektor af tekstelementer
drikkevarer <- c("vand","kaffe","te")

Man bruger funktionen c() til at samle de enkelte elementer (c = combine)

Begrænsningen ved vektorer er, at de kun kan indeholde en datatype. Hvis man sætter tal ind i en vektor, som ellers indeholder tekst, vil tallene også blive lavet om til tekst. Dog kan elementer godt have værdien NA, uanset datatype.

Lists

Lists fungerer på mange måder ligesom vektorer, men kan indeholde forskellige datatyper

minListe <- list("kaffe","vand", 42, TRUE)

Matrix

Matrix er en struktur, som kan indeholde data af samme datatype (ligesom vektorer), som har to dimentioner (altså rækker og koloner).

minMatrix <- matrix(c(1,2,3,4,5,6), nrow = 3, ncol = 2)
minMatrix
##      [,1] [,2]
## [1,]    1    4
## [2,]    2    5
## [3,]    3    6

Matrix bruges ofte til analyser af tabulerede data, fx 2x2 tabeller ved chi2 eller fisher’s exact test.

Arrays

Arrays minder om matrix, men har mere end to dimentioner. Disse bruges dog sjældent indenfor sundhedsforskning og gennemgås derfor ikke yderligere.

Data Frames

En dataframe er en datastruktur, som organiserer data i rækker og kolonner, ligesom et regneark. Teknisk set er det en liste af vectors, som er koblet med hinanden i begge dimentioner.

Hver kolone er derved en variabel med en titel (header), hvor alle elementer har samme datatype og som har samme længde som de andre kolonner i dataframen.

Hver række består af data, som er koblet på tværs af kolonnerne - og kan fx repræsentere samme patient eller observation.
Data frames er en af de mest anvendte datastrukturer i R til håndtering af datasets.

# lav en dataframe
minDataFrame <- data.frame (
  patientnr = c(1,2,3,4,5,6,7,8),
  diagnose = c("I61", "I61", "I61", "I64", "I64", "I63", "I64", "I61"),
  sysBT = c(142, 165, 153, 120, 145, 168, 165, 133),
  køn = factor(c(1,1,2,1,2,2,2,1), labels = c("Mand","Kvinde"))
)

# vis dataframe
minDataFrame
##   patientnr diagnose sysBT    køn
## 1         1      I61   142   Mand
## 2         2      I61   165   Mand
## 3         3      I61   153 Kvinde
## 4         4      I64   120   Mand
## 5         5      I64   145 Kvinde
## 6         6      I63   168 Kvinde
## 7         7      I64   165 Kvinde
## 8         8      I61   133   Mand
# Visning af en specifik variabel (kolone)
minDataFrame$sysBT
## [1] 142 165 153 120 145 168 165 133
# eller
minDataFrame[3]
##   sysBT
## 1   142
## 2   165
## 3   153
## 4   120
## 5   145
## 6   168
## 7   165
## 8   133
# Visning af en observation (række)
minDataFrame[4,]
##   patientnr diagnose sysBT  køn
## 4         4      I64   120 Mand
# Tilføj en ny variabel
minDataFrame$alder <- c(54,89,78,52,80,66,71,90)
# Tilføj en ny observation (række)
minDataFrame <- rbind(minDataFrame, list(9,"I63",145,"Mand",78))

Bemærk at man skal bruge funktionen list() når den nye observation indeholder blandede datatyper - hvis man bruger c() vil alle de numeriske variable blive lavet om til tekst.

Matematiske funktioner i R

R kan bruges både som en simpel regnemaskine og til at lave mere komplekse matematiske beregninger.

De basale operatorer er + , - , * og /

5 + 5
## [1] 10
5 - 5
## [1] 0
5 * 5
## [1] 25
5 / 5
## [1] 1

Man kan også regne med elementer

tal <- 8
tal + 10
## [1] 18

Derudover har R en række indbyggede matematiske funktioner

sqrt(100)
## [1] 10
4^2
## [1] 16
log(2)
## [1] 0.6931472
exp(1)
## [1] 2.718282
sum(2,18,5)
## [1] 25

Man kan også kombinere forskellige beregninger og styre rækkefølgen af beregningerne ved at indsætte parenteser.

højde <- 175
vægt <- 70
bmi <- vægt/(højde/100)^2
  
bmi
## [1] 22.85714

Denne type af beregninger kan også bruges på vektorer af data, og ikke kun på enkelt værdier - outputtet vil i såfald også være en vektor af værdier:

højde <- c(160,158,159,177,159,172,163,172,176,162)
vægt <- c(67,84,90,94,73,78,86,75,71,89)
bmi <- vægt/(højde/100)^2
  
bmi
##  [1] 26.17187 33.64845 35.59986 30.00415 28.87544 26.36560 32.36855 25.35154
##  [9] 22.92097 33.91251

Vi kan bruge samme princip til at udregne nye variable i en dataframe:

ernæring <- data.frame(
  højde = c(160,158,159,177,159,172,163,172,176,162),
  vægt = c(67,84,90,94,73,78,86,75,71,89)
)
ernæring$bmi <- ernæring$vægt/(ernæring$højde/100)^2
ernæring
##    højde vægt      bmi
## 1    160   67 26.17187
## 2    158   84 33.64845
## 3    159   90 35.59986
## 4    177   94 30.00415
## 5    159   73 28.87544
## 6    172   78 26.36560
## 7    163   86 32.36855
## 8    172   75 25.35154
## 9    176   71 22.92097
## 10   162   89 33.91251

Logiske og betingede udtryk

Når man håndterer data og laver statistiske analyse, vil man ofte have brug for at dele data op eller opstille betingelser for hvad der skal foregå. Det gør man ved at teste om en given betingelse er sand eller falsk (TRUE/FALSE).

Logiske operatorer

R har en række logiske udtryk vi kan bruge til at teste værdier mod hinanden:

10 < 12  # TRUE fordi 10 er mindre end 12
## [1] TRUE
10 <= 12 # TRUE fordi 10 er mindre end ELLER lig med 12
## [1] TRUE
10 > 12  # FALSE fordi 10 ikke er større end 12
## [1] FALSE
10 >= 12 # FALSE fordi 10 ikke er større end ELLER lig med 12
## [1] FALSE
10 == 12 # FALSE fordi 10 ikke er lig med 12
## [1] FALSE
10 != 12 # TRUE fordi 10 ikke er lig med 12
## [1] TRUE

Vi kan lave samme sammenligninger med variable

a <- 10
b <- 12

a < b  # TRUE fordi 10 er mindre end 12
## [1] TRUE
a <= b # TRUE fordi 10 er mindre end ELLER lig med 12
## [1] TRUE
a > b  # FALSE fordi 10 ikke er større end 12
## [1] FALSE
a >= b # FALSE fordi 10 ikke er større end ELLER lig med 12
## [1] FALSE
a == b # FALSE fordi 10 ikke er lig med 12
## [1] FALSE
a != b # TRUE fordi 10 ikke er lig med 12
## [1] TRUE

if…else

Herefter kan vi bruge de logiske tests til at opstille betingelser og tilknytte forskellige udfald afhængigt af om udfaldet er TRUE eller FALSE.

Funktionerne if() og else() kan fx bruges til at udfører en handling hvis betingelsen er opfyldt og en alternativ handling hvis den ikke er opfyldt.

a <- 10
b <- 12

if (a < b){
  print("a er mindre end b")
} else {
  print("a er større end b")
}
## [1] "a er mindre end b"

Vi kan også lave længere rækker af betingelser

a <- 10
b <- 10

if (a < b){
  print("a er mindre end b")
} else 
if (a == b){
  print("a er det samme som b")
} else {
  print("a er større end b")
}
## [1] "a er det samme som b"

R har også en break funktion, som nogle gange bruges til at begrænse loop funktioner. Men den er ikke nødvendig, at have med ved if..else udsagn, som det ellers kan være i andre programmeringssprog.

ifelse()

Funktionen ifelse() kan bruges til at give en værdi, hvis en betingele er opfyldt eller en anden værdi hvis betingelsen ikke er opfyldt.

ifelse(*test*, *TRUE*, *FALSE*)
# Tester om a er større end tærskelværdien på 1000
# Returnerer 'Ja' hvis TRUE og 'Nej' hvis FALSE
a <- 7562
ifelse(a > 1000, "Ja","Nej")
## [1] "Ja"

Vi kan også lave nested betingelser, altså betingede udtryk inde i betingede udtryk - men det bliver hurtigt uoverskueligt

# Tester om a er større end tærskelværdien på 1000
# Returnerer 'Ja' hvis TRUE, men hvis første betingelse er FALSE testes en ny
# betingelse om a er over 500
a <- 756
ifelse(a > 1000, "Ja", ifelse(a > 500, "Grænseværdi","Nej"))
## [1] "Grænseværdi"

Metoden kan også bruges på en vektor af data

# tester om sidste tal i et cpr-nummer er lige 
# returnerer værdien "Kvinde" hvis TRUE og ellers "Mand
cpr <- c("121158-4545","191149-6988","210594-7597","160716-2241","131054-6656")
ifelse(
  as.numeric( substr( cpr, nchar( cpr ), nchar( cpr ))) %% 2 == 0, 
  "Kvinde", "Mand")
## [1] "Mand"   "Kvinde" "Mand"   "Mand"   "Kvinde"

Flere betingelser

I nogle tilfælde skal der bruges flere betingelser, hvor enten alle eller nogle betingelser skal være opfyldt. Her skal hver betingelser være adskilt af en operator, som angiver om begge eller kun en betingelse er opfyldt.

Hvis begge (eller alle) betingelser skal være opfyldt adskilles udtrykkende af operatoren & (OG)

a <- 1000
b <- 25
c <- 756

if(a > b & a > c){ print("begge betingelser er opfyldt") }
## [1] "begge betingelser er opfyldt"

Hvis kun en betingelser behøver at være opfyldt adskilles udtrykkende af operatoren | (ELLER)

a <- 1000
b <- 25
c <- 1756

if(a > b | a > c){ print("mindst en betingelse er opfyldt") }
## [1] "mindst en betingelse er opfyldt"

Funktioner

De fleste af de opgaver vi laver i R udfører vi via funktioner. Funktioner er en blok af kode, som bliver givet noget input og nogle parametre, og herefter giver et output.

Strukturen for funktioner er navnet på funktionen, efterfulgt af en parentes, hvor man skriver de argumenter/parametre, der skal anvendes af funktionen:

functionName(argument1, argument2, ...)

Funktionerne er programmeret til at tage argumenterne i en bestemt rækkefølge. Hvis funktionen kun skal bruge få argumenter kan man, derfor, ofte nøjes med at skrive argumenterne uden at navngive dem.

Eksempel:

Funktionen sample() bruges til at lave tilfældige trækninger fra en vektor. Funktionen skal bruge tre argumenter:

  • x (vektoren der trække fra; her defineret med 1:10, som laver en vektor af heltal fra 1 til 10),
  • size (antallet af trækninger) og
  • replace (om trækningen skal være med eller uden tilbagelægning; angives TRUE/FALSE):
# Her udfyldes med navne
sample(x = 1:10, size = 20, replace = TRUE)
##  [1]  7  3  1  8  7 10 10  5  7  5  3  3  7  5  1  4  9 10  5  3
# Dette er tilstrækkeligt, så kænge rækkefølgen bibeholdes
sample(1:10, 20, T)
##  [1]  6  8  6  3  2  7  4  9  3  2  1  7  4  4  5 10 10 10  7  5
# Hvis man kalder argumenterne med deres navn er rækkefølgen ligegyldig
sample(replace = TRUE, size = 20, x = 1:10)
##  [1]  6  2  8  2  5  1  7  2 10  5 10 10 10  4  1  8 10  7  6  7

Biblioteker og pakker

BaseR indholder rigtig mange funktioner, som hele tiden vil være tilgængelige.

Men nogle gange har vi brug for at lave ting i R, som ikke er en del af standardpakken. Her findes der en lang række af ekstra pakker eller biblioteker af funktioner, som man kan installere og bruge.

R har fx ikke en indbygget funktion, som kan fortælle os hvilken fase månen er i på en given dato.

Det er der heldigvis andre som har haft brug for - og de har derfor lavet en pakke, som vi kan installere og bruge ({lunar})

Vi kan enten installere direkte med en kommando

install.packages("lunar")

eller via menuen: Tools > Install packages…

Når vi herefter skal bruge en funktion i dette bibliotek, er vi nødt til at fortælle R, at funktionen ligger i et andet bibliotek, enten ved at aktiverer biblioteket med funktionen library() eller ved at specificere biblioteket med dobbeltkolon ::.

Pakker skal kun installeres én gang på computeren. Bibliotekerne skal derimod aktiveres på ny hver gang man åbner programmet, fx ved at køre library() funktionen.

Hvis jeg vil bruge funktionen lunar.phase() i biblioteket {lunar} kan jeg gøre det på en af disse måder

# Datoer
datoer <- c("07-02-25","24-11-25", "17-02-25", "09-02-25", 
            "09-05-25","02-06-25", "12-11-25", "02-03-25")

# Aktiverer biblioteket - hvis man skal bruge den flere gange
library(lunar)
lunar.phase( as.POSIXlt(datoer, format="%d-%m-%y"), name = TRUE )
## [1] Waxing Waxing Waning Full   Full   Waxing Waning New   
## Levels: New Waxing Full Waning
# Specificere biblioteket med dobbeltkolon 
lunar::lunar.phase( as.POSIXlt(datoer, format="%d-%m-%y"), name = TRUE )
## [1] Waxing Waxing Waning Full   Full   Waxing Waning New   
## Levels: New Waxing Full Waning

library() er god at bruge, hvis man skal bruge funktioner fra samme bibliotek flere gange i samme dokument. Dobbelt-kolon metoden er god hvis man kun skal bruge funktioner fra samme bibliotek få gange og til at sikre, at et stykke kode stadig virker, hvis man glemmer aktivere biblioteket når man åbner et dokument.

Skiv din egen funktion

For at skrive sin egen funktion bruger man function() udtrykket, med følgende syntax

| functionName <- function(arg1, arg2, ...){ action }

Eksempel

Vi kan fx lave en funktion, som når vi kalder den, skriver “Hello World”:

minFunktion <- function(){ print("Hello World") }
minFunktion()
## [1] "Hello World"

Scope af variable i funktioner

Variable, som deklareres inde i en funktion har i udgangspunktet kun et lokalt scope. Det betyder at værdien kun kan bruges inden for funktionen, men ikke af andre funktioner.

diagnose <- "Hjerneinfarkt DI639"

minFunktion <- function(){ 
  diagnose <- "Hjerneblødning DI619" } # Ændre værdien af variablen 'diagnose'
minFunktion()

diagnose
## [1] "Hjerneinfarkt DI639"

Selvom variablen ‘diagnose’ ændres til ‘Hjerneblødning DI619’ ind i funktionen, vil variablen stadig have værdien ‘Hjerneinfarkt DI639’ i det globale miljø, fordi ændringen kun sker lokalt inde i funktionen.

Fordelen ved at holde værdier lokalt inde i funktioner er, bl.a., at vi kan undgå at det globale miljø fyldes op af en masse mellemregninger, som vi ikke skal bruge til noget senere.

Hvis man har brug for, at en funktion ændre en værdi med globalt score kan man bruge en dobbeltpil <<- til at deklarere variablen med

diagnose <- "Hjerneinfarkt DI639"

minFunktion <- function(){ 
  diagnose <<- "Hjerneblødning DI619" } # Ændre værdien af variablen 'diagnose'
minFunktion()

diagnose
## [1] "Hjerneblødning DI619"

Vi kan også bruge vores funktion til at ændre variablen:

diagnose <- "Hjerneinfarkt DI639"

minFunktion <- function(){ 
  diagnose <- "Hjerneblødning DI619" } # Ændre værdien af variablen 'diagnose'

# Ændrer værdien af 'diagnose' til udfaldet af vores funktion
diagnose <- minFunktion() 

diagnose
## [1] "Hjerneblødning DI619"

Definition af argumenter i funktionen

I ovenstående eksempel er der ikke noget input, og der defineres derfor ingen argumenter.

Hvis vi skal bruge argumenter inde i funktionen, definerer vi dem i parentesen efter function udsagnet:

mitGennemsnit <- function(x){
  # Udregner gennemsnittet af vektoren x ved at dividere summen af x
  # med længden af x
  gennemsnit <- sum(x) / length(x)
  # returnere værdien af variablen 'gennemsnit'
  return(gennemsnit)
}
mitGennemsnit(c(12,25,58))
## [1] 31.66667
# Med NA værdi
mitGennemsnit(c(12,25,58,NA))
## [1] NA

For at funktionen kan returnere et output bruger vi funktionen return().

Bemærk at funktionen returnere ‘NA’ hvis vektoren indeholder en NA værdi. For at undgå dette kan vi udbygge funktionen:

mitGennemsnit <- function(x){
  # fjerner Na værdier 
  x2 <- na.omit(x)
  # Udregner gennemsnittet af vektoren x ved at dividere summen af x
  # med længden af x
  gennemsnit <- sum(x2) / length(x2)
  # returnere værdien af variablen 'gennemsnit'
  return(gennemsnit)
}
mitGennemsnit(c(12,25,58))
## [1] 31.66667
# Med NA værdi
mitGennemsnit(c(12,25,58,NA))
## [1] 31.66667

Sammensatte outputs

Men nu ved vi ikke hvor mange værdier gennemsnittet er baseret på. Det kan vi så angive i outputtet:

mitGennemsnit <- function(x){
  # fjerner Na værdier 
  x2 <- na.omit(x)
  # Udregner antallet af NA's
  nas <- length(x) - length(x2)
  # Udregner gennemsnittet af vektoren x ved at dividere summen af x
  # med længden af x
  gennemsnit <- sum(x2) / length(x2)
  # returnere værdien af variablen 'gennemsnit' med forklaring
  return(cat("Gennemsnit:",gennemsnit,"(NA's omitted:",nas,")\n")  )
}
mitGennemsnit(c(12,25,58))
## Gennemsnit: 31.66667 (NA's omitted: 0 )
# Med NA værdi
mitGennemsnit(c(12,25,58,NA))
## Gennemsnit: 31.66667 (NA's omitted: 1 )

Her er outputtet, der returneres sammensat af både tekst og tal, via funktionen cat() (cat = concatenate). Hvis man vil have et tekst-output kan man alternativt bruge funktionerne paste() eller paste0().

Flow control i funktioner

Det kan nogle gange være nyttigt at bruge betingelser til at styre hvad der foregår inde i en funktion. Det kan fx være at man vil bruge et argument til at styre hvordan funktionen håndtere input eller output.

Vi kunne fx lave et argument, som styrer om antallet af NAs vises i output eller ej, ved at indsætte argumentet ‘numberOfNas’. Ved at definerer det som numberOfNas = TRUE sættes det til TRUE som default. Derfor vises antallet af NAs i outputtet, med mindre at vi aktivt sætter argumentet til FALSE.

mitGennemsnit <- function(x, numberOfNas = TRUE){
  # fjerner Na værdier 
  x2 <- na.omit(x)
  nas <- length(x) - length(x2)
  # Udregner gennemsnittet af vektoren x ved at dividere summen af x
  # med længden af x
  gennemsnit <- sum(x2) / length(x2)
  
  # flow kontrol
  if(numberOfNas == FALSE){
  # returnere værdien af variablen 'gennemsnit' UDEN NA's
  return(cat("Gennemsnit:",gennemsnit,"\n")  )  
  } else {
  # returnere værdien af variablen 'gennemsnit' med forklaring
  return(cat("Gennemsnit:",gennemsnit,"(NA's omitted:",nas,")\n")  ) }
}

# Viser antal NAs
mitGennemsnit(c(12,25,58,NA))
## Gennemsnit: 31.66667 (NA's omitted: 1 )
# Viser ikke antal NAs
mitGennemsnit(c(12,25,58,NA), numberOfNas = FALSE)
## Gennemsnit: 31.66667

Bemærk at der bruges et enkelt lighedstegn = når man definere argumentet, men dobbelt lighedstegn == når man tester betingelsen!

Vi kan også bruge flow kontrol til at teste om parametrene, der kommer ind i funktionen er i et korrekt format, og returnere en fejlmeddelelse, hvis formatet ikke passer. I nedenstående funktion tælles hvor mange gang et givent bogstav (argument a) forekommer i en tekststreng (argument x). Hvis man forsøger at sætte værdier ind, som ikke er tekst, returneres en fejlmeddelse om at variablen ikke er tekst.

myFUN <- function(x, a){
  # tester om første argument er tekst
  if(class(x) != "character"){return("ERROR: x is not text")} else 
  # tester om andet argument er tekst  
  if(class(a) != "character"){return("ERROR: a is not text")} else
  # udfører test hvis begge argumenter er tekst  
   {
  x = tolower(x)  # gør alle bogstaver små
  xSplit = unlist(strsplit(x, split ="")) # splitter x op i enkelte bogstaver
  l = length(xSplit) # længden af x
  output = NULL # definere variablen output
  # tester hvert bogstav om det er a - returnere 1 hvis TRUE og 0 hvis FALSE
  for (i in 1:l) {output[i] = ifelse(xSplit[i] == a,1,0)} 
  return(sum(output))} # summere alle 1'erne
}
myFUN("blueberry","b")
## [1] 2
myFUN("Blueberry","b")
## [1] 2
myFUN(1,"b")
## [1] "ERROR: x is not text"
myFUN("blueberry",1)
## [1] "ERROR: a is not text"

Import og export af data

Langt det meste af det arbejde vi laver i R, fx statistiske analyser og datavisualisering, er baseret på større dataset. Dette kræver selvfølgeligt, at vi får data ind i R.

Metoderne vi bruger til at importere data afhænger af hvilket format data er gemt i.

Plain text filer (csv og txt)

De nemmeste formatter at arbejde med er *.csv (comma separated values) og *.txt (ren tekst). Begge disse formatter er ren tekst (plain text) uden formatteringer, hvor den primære forskel er at *.csv bruger tegn (komma eller semikolon) til at adskille værdierne og *.txt bruger mellemrum eller tabulering til at adskille værdierne.

R har en indbygget funktion til at læse disse filer:

  • read.csv() (Bruges hvis værdier er adskilt med komma eller semikolon)
  • read.delim() (Bruges hvis værdier er adskilt med tabulation)

Før man importerer data fra csv eller txt er man nødt til at vide noget om formatteringen. R har nogle default indstillinger, som den antager når den indlæser filen. Men hvis filen er formatteret anderledes vil indlæsningen fejle.

Default indstillingen for csv-filer er fx at værdierne er adskilt af kommaer og decimaler er adskilt med punktum (fordi det gør man på engelsk). Men hvis csv-filen er gemt i en dansk version af Excel vil værdierne være afskilt af semikolon og decimaler afskilt med komma. I det tilfælde vil vi være nødt til at fortælle R, hvordan filen er formatteret.

Vi skal derfor undersøge om:

  • Er første linje navnene (headings) på variablene,
  • Hvordan er værdierne adskilt med hinanden, fx komma, semikolon, mellemrum
  • Hvordan er manglende værdier angivet, fx NA, punktum, blankt felt
  • Hvilket tegn er brugt til at adskille decimaler, fx punktum eller komma

Hvis første linje i filen er navnene på variablene, bliver disse brugt til at navngive variablene i dataframen (Dette er default indstillingen). Hvis først linje ikke er navne, men data, skal vi definere argumentet header = FALSE.

Default indstillingen for separateren (tegnet der adskiller værdierne) er et komma (,). Hvis værdierne er adskilt med semi-kolon skal vi definere argumentet sep = ";" – hvis det er mellemrum sep = " " og hvis det er tabulation sep = "\t".

R vil per default læse værdien NA som en manglende værdi. Men hvis manglende værdier er angivet på andre måder skal dette defineres med argumentet na.strings. Hvis fx manglende værdier er angivet med punktumer bliver argumentet na.strings = ".".

Default indstillingen for decimal-separateren er punktum. Hvis der bruges andet, oftest komma, defineres argumentet dec = ",".

Indlæsning af fil i samme bibliotek

Hvis filen ligger i samme mappe på computen, som R-filen behøver man kun at skrive fil-navnet

# Indlæsning af fil i engelsk formattering
strokedf <- read.csv("strokedata.csv")
# Indlæsning af fil i dansk formattering, altså 
# semikolon mellem værdier og komma som decimal separator
strokedf <- read.csv("strokedata_dk.csv", sep=";", dec = ",")

Indlæsning af fil fra andet bibliotek

Hvis filen ligger i en anden mappe, fx på et fælles drev, skal vi specificere hele fil-stien

# Indlæsning af fil fra et fælles drev
strokedf <- read.csv("L:\\AuditData\\ApoData\\strokedata.csv")

Bemærk at der bruges dobbelt back slash til adskillelse af niveauerne. Dette er den mest stabile metode på Windows computere, men det fungerer nogle gange anderledes på andre styresystemer.

Indlæsning af fil fra internettet

Hvis data er tilgængeligt på internettet kan vi bruge http-adressen som filnavn

strokedf <- read.csv("https://jacobliljehult.github.io/research/strokedata.csv", 
                       sep=",", header=T, stringsAsFactors = TRUE)

Indlæsning af andre formatter

Indlæsning af plain text formatter er klart det letteste i baseR.

Hvis man har brug for at indlæse andre filformatter findes der også pakker, som kan håndtere dette. Fx findes pakken {readxl} til indlæsning af Excel-formatter, som fungerer rimeligt.

Export af data

Nogle gange har vi også brug for at eksportere data fra R. Vi kan fx have et datasæt, som er renset og formatteret, og som vi godt vil bruge senere, uden at skulle rense og formattere det igen. Dette kan fx gøres med funktionen write.csv()

| write.csv(x, file, row.names = FALSE, quote = FALSE)

Hvor argumenterne er:

  • x - den dataframe man vil eksportere
  • file - filnavnet der skal eksporteres til
  • row.names - forhindre at række navnene bliver lavet til en variabel
  • quote - fjerner citationstegn omkring tekstelementer
write.csv(strokedf, file =  "stroke_data.csv", row.names = FALSE, quote = FALSE)

Plots

R har en række indbyggede funktioner til hurtigt at tegne plots. De kan være udemærkede til at lave en hurtigt visualisering af data - fx hvis man visuelt vil inspicere data for outliers.

Skal man lave mere avancerede grafer, fx til publikation, vil jeg anbefale, at man i stedet bruger pakken {ggplot2}, som er en del af TidyVerse. ggplot2 har mange flere funktioner og giver bedre kontrol over hvordan figuren renderes - og kan derfor tegne pænere figurer. Derudover har ggplot2 også funktionen ggsave, som giver mulighed for at eksportere figurer direkte i de formater og opløsninger man har brug for.

Punkter med plot()

Den simpleste figur vi kan tegne er en figur med et eller flere punkter.

# Et punkt med koordinatet x = 1, y = 3
par(mar = c(4, 4, 1, 1)) # Laver mindre magniner omkring figur
plot(1,3)

Hvis input er vektorer (af samme længde) får vi et scatter plot

# Flere punkter ved brug af vektorer
par(mar = c(4, 4, 1, 1)) # Laver mindre magniner omkring figur
plot(c(1,3,3,6,6,8,8,9,10,10), c(1,1,3,3,4,4,5,6,7,9))

Størrelsen og formen på punkterne kan ændres med henholdsvis argumenterne cex og pch

# Farve på plottet
par(mar = c(4, 4, 1, 1)) # Laver mindre magniner omkring figur
plot(c(1,3,3,6,6,8,8,9,10,10), c(1,1,3,3,4,4,5,6,7,9), col="blue",
     cex = 2, # punkter i dobbelt størrelse
     pch = 2, # trekanter i stedet for cirkler
     xlab="X-akse", ylab="Y-akse")

De forskellige shapes

Formen af punkterne kan ændres ved at definere pch arguemtet med en af følgende værdier:

Linjer

Hvis vi skal tegne en linje kan vi tilføje argumentet type = "l" til plot() funktionen.

# Linje i stedet for punkter
par(mar = c(4, 4, 1, 1)) # Laver mindre magniner omkring figur
plot(c(1,3,3,6,6,8,8,9,10,10), c(1,1,3,3,4,4,5,6,7,9), type="l")

Vi kan bruge argumentet col = "farve" til at skifte farve på punkter eller linjen.

# Farve på plottet
par(mar = c(4, 4, 1, 1)) # Laver mindre magniner omkring figur
plot(c(1,3,3,6,6,8,8,9,10,10), c(1,1,3,3,4,4,5,6,7,9), type="l", col="red",
     xlab="X-akse", ylab="Y-akse")

På linjer kan vi ændre tykkelse af linjen med lwd og typen med lty.

# Farve på plottet
par(mar = c(4, 4, 1.5, 1)) # Laver mindre magniner omkring figur
plot(c(1,3,3,6,6,8,8,9,10,10), c(1,1,3,3,4,4,5,6,7,9), type="l", col = "blue",
     lwd = 5, # tykkere linje
     lty = 3, # linjetype = stiplet linje
     main="Min Graf", xlab="X-akse", ylab="Y-akse")

De forskellige linjetyper

Typen af linje kan ændres ved at definere lty argumentet med en af følgende værdier:

# library(ggplot2)
#linjetyper <- 0:6
ggplot() +
  geom_hline(yintercept = 0:6, lty = 0:6, linewidth = 1) +
  scale_y_reverse(breaks = 0:6) + theme_minimal() + 
  theme(panel.grid = element_blank(),
        axis.text = element_text(size = 14))

Titler og akse labels

Vi kan tilføje en titel (main="titel") og labels til akserne (xlab = "label" & ylab = "label").

# Titel og labels
par(mar = c(4, 4, 1.5, 1)) # Laver mindre magniner omkring figur
plot(c(1,3,3,6,6,8,8,9,10,10), c(1,1,3,3,4,4,5,6,7,9), type="l",
     main="Min Graf", xlab="X-akse", ylab="Y-akse")

Barplot

Hvis den ene akse er diskret kan vi også lave søjlediagrammer ved hjælp af barplot(), hvor x-aksen er en tekst-variabel og y-aksen er en numerisk variabel.

| barplot(y, names.arg = x)

par(mar = c(2.5, 2.5, 1, 1)) # Laver mindre magniner omkring figur
barplot(c(2,3,1,7), names.arg = c("A","B","C","D"))

Pie diagrammer

En anden måde at vise denne type data er et lagkagediagram, som kan laves med funktionen pie().

par(mar = c(1, 1, 0.5, 0.5)) # Laver mindre magniner omkring figur
pie(c(20,30,10,50))

Default indstillingen i pie() er at det starter kl. 3 på skiven og gå mod uret. Startpunktet kan vi ændre med argumentet init.angle, hvor man angiver vinklen i grader (0 - 360).

Vi kan ændre farverne på kategorierne med argumentet col (en vektor af tekst med samme længde som data).

Vi kan tilføje labels til kategorierne med argumentet label (en vektor af tekst med samme længde som data).

par(mar = c(1, 2, 1, 2.5)) # Laver mindre magniner omkring figur
pie(c(20,30,10,50),
    init.angle = 45,  # Start vinkel
    col = c("blue","darkgreen","cyan","red"), # farver
    label = c("Læger","Terapeuter","Sekretærer","Sygeplejersker")) # labels

Histogram

Hvis vi skal undersøge fordelingen af en variabel kan vi lave et histogram funktionen hist()

# Tilfældig normalfordelt data
x <- rnorm(500, mean = 100, sd = 15)

# Histogram
par(mar = c(4, 4, 1.5, 1)) # Laver mindre magniner omkring figur
hist(x)

Hvis vi vil tilføje en fordelingskurve til histogrammet, skal vi både tilføje argumentet prob = TRUE til histogrammet og funktionen lines(density(x)).

par(mar = c(4, 4, 2, 1)) # Laver mindre magniner omkring figur
hist(x, prob = TRUE)
lines( density(x) )

QQ-plot

En anden måde vi kan undersøge fordelingen af en variabel er et qq-plot, hvilket vi kan lave med funktionen qqnorm().

par(mar = c(4, 4, 1, 1)) # Laver mindre magniner omkring figur
qqnorm(x)

Hvis vi vil tilføje en referencelinje tilføjer vi funktionen qqline().

par(mar = c(4, 4, 1, 1)) # Laver mindre magniner omkring figur
qqnorm(x)
qqline(x)

Boxplot

Hvis vi vil sammenligne to eller flere grupper kan vi lave et boxplot med funktionen boxplot()

# Tilfældige data med to behandlingsgrupper og normalfordelte testresultater
# Resultaterne i behandlingsgruppen plusses alle med 1 SD
boxdata <- data.frame(
  allocation = factor(sample(1:2, 500, T), labels = c("Behandling","Placebo")))
boxdata$resultat = ifelse(boxdata$allocation == "Placebo", 
                          rnorm(250, mean = 100, sd = 15), 
                          rnorm(250, mean = 100, sd = 15)+15) 

# Boxplot
par(mar = c(4, 4, 1, 1)) # Laver mindre magniner omkring figur
boxplot( resultat ~ allocation, data = boxdata)


Kontaktoplysninger

Jacob Liljehult

Klinisk sygeplejespecialist

cand.scient.san, PhD

Neurologisk afdeling Nordsjællands Hospital

Dyrehavevej 29 3400 Hillerød

E-mail:

Website: https://jacobliljehult.github.io/research/