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:
## [1] 10
eller denne kommando:
## [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.
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).
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.
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)
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.
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
## [1] 10 20 30 40 50 60 70 80 90 100
## [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.
## [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 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:
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
## [1] "Hello World"
R har også en print() funktion - som man kender det fra andre programmeringssprog - men den er i princippet unødvendig:
## [1] "Hello World"
Der er nogle få regler for input, når man definerer variable:
TRUE
og FALSE
bruges kun til
logiske variable (Det samme gælder T
og F
, som
bruges som short-hand for TRUE
og FALSE
)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.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).
Hvis man har brug for at slette en variabel, kan man bruge
remove funktionen {rm()}
:
Dette bruges fx til at rydde op i det globale miljø, så man ikke mister overblikket.
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:
I de fleste tilfælde kan R godt genkende den korrekte type, men ellers kan man ‘tvinge’ et format igennem:
Factors er kategoriske variable med et defineret antal niveauer og eventuelt labels
## [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
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:
## [1] "2020-10-08 CEST"
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.
Den simpleste datastruktur er en vektor, som kan indeholde en række data af samme datatype i en dimension.
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 fungerer på mange måder ligesom vektorer, men kan indeholde forskellige datatyper
Matrix er en struktur, som kan indeholde data af samme datatype (ligesom vektorer), som har to dimentioner (altså rækker og koloner).
## [,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 minder om matrix, men har mere end to dimentioner. Disse bruges dog sjældent indenfor sundhedsforskning og gennemgås derfor ikke yderligere.
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
## [1] 142 165 153 120 145 168 165 133
## sysBT
## 1 142
## 2 165
## 3 153
## 4 120
## 5 145
## 6 168
## 7 165
## 8 133
## patientnr diagnose sysBT køn
## 4 4 I64 120 Mand
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.
R kan bruges både som en simpel regnemaskine og til at lave mere komplekse matematiske beregninger.
De basale operatorer er +
, -
,
*
og /
## [1] 10
## [1] 0
## [1] 25
## [1] 1
Man kan også regne med elementer
## [1] 18
Derudover har R en række indbyggede matematiske funktioner
## [1] 10
## [1] 16
## [1] 0.6931472
## [1] 2.718282
## [1] 25
Man kan også kombinere forskellige beregninger og styre rækkefølgen af beregningerne ved at indsætte parenteser.
## [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
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).
R har en række logiske udtryk vi kan bruge til at teste værdier mod hinanden:
## [1] TRUE
## [1] TRUE
## [1] FALSE
## [1] FALSE
## [1] FALSE
## [1] TRUE
Vi kan lave samme sammenligninger med variable
## [1] TRUE
## [1] TRUE
## [1] FALSE
## [1] FALSE
## [1] FALSE
## [1] TRUE
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.
## [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.
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"
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)
## [1] "begge betingelser er opfyldt"
Hvis kun en betingelser behøver at være opfyldt adskilles udtrykkende
af operatoren |
(ELLER)
## [1] "mindst en betingelse er opfyldt"
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:
1:10
, som laver en vektor af heltal fra 1 til 10),## [1] 7 3 1 8 7 10 10 5 7 5 3 3 7 5 1 4 9 10 5 3
## [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
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
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.
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”:
## [1] "Hello World"
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"
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
## [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
## [1] 31.66667
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 )
## 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()
.
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 )
## 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
## [1] 2
## [1] "ERROR: x is not text"
## [1] "ERROR: a is not text"
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.
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:
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 = ","
.
Hvis filen ligger i samme mappe på computen, som R-filen behøver man kun at skrive fil-navnet
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.
Hvis data er tilgængeligt på internettet kan vi bruge http-adressen som filnavn
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.
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:
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 afTidyVerse
.ggplot2
har mange flere funktioner og giver bedre kontrol over hvordan figuren renderes - og kan derfor tegne pænere figurer. Derudover harggplot2
også funktionenggsave
, som giver mulighed for at eksportere figurer direkte i de formater og opløsninger man har brug for.
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")
Formen af punkterne kan ændres ved at definere pch
arguemtet med en af følgende værdier:
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")
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))
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")
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"))
En anden måde at vise denne type data er et lagkagediagram, som kan
laves med funktionen pie()
.
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
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) )
En anden måde vi kan undersøge fordelingen af en variabel er et
qq-plot, hvilket vi kan lave med funktionen qqnorm()
.
Hvis vi vil tilføje en referencelinje tilføjer vi funktionen
qqline()
.
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)
Jacob Liljehult
Klinisk sygeplejespecialist
cand.scient.san, PhD
Neurologisk afdeling Nordsjællands Hospital
Dyrehavevej 29 3400 Hillerød
E-mail: jacob.mesot.liljehult@regionh.dk
Website: https://jacobliljehult.github.io/research/