Tilbage til menu

Introduktion

Før man kan begynde at lave analyser i R er man nødt til at have noget data, at arbejde med. Første skridt er, at vi skal have data ind i programmet - og herefter skal man sikre sig, at data er korrekt formatteret i forhold til de analyser, man ønsker at lave, og at data ikke indeholder fejl.

Import af datasæt

Første skridt er at importere data fra en fil til R.

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 = ",".

I denne guide bruges et datasæt med simulerede data (hvor nogle af variablene indeholder fejl, som skal rettes). Man se nederst i guiden hvordan de simulerede data er genereret.

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

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. Plaintext filer kan også indlæses med pakken {readr} (som er en del af Tidyverse), som giver nogle flere muligheder for at specificere parametre, fx styring af dataformater for hver variabel.

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. Ligeledes findes der også pakker, der kan importere data fra andre statistikprogrammer, fx SPSS, SAS eller STATA.

Kontroller formater

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.

Hvis man vil vide hvilke format variablene er indlæst i, kan man få en liste med funktionen str().

str(testDataRaw)
## 'data.frame':    100 obs. of  17 variables:
##  $ id            : int  1 2 3 4 5 6 7 8 9 10 ...
##  $ hospital      : num  2 4 3 1 4 3 3 3 5 2 ...
##  $ admission_date: chr  "2024-01-04" "2024-01-18" "2024-01-20" "2024-01-22" ...
##  $ admission_time: chr  "15:02" "13:50" "13:56" "14:17" ...
##  $ discharge_date: chr  "2024-01-12" "2024-01-25" "2024-01-24" "2024-01-31" ...
##  $ discharge_time: chr  "14:23" "09:31" "19:24" "14:10" ...
##  $ gender        : chr  "F" "M" "M" "F" ...
##  $ age           : num  85 72 61 65 54 72 72 79 72 77 ...
##  $ height        : num  163 162 176 166 167 180 174 175 183 170 ...
##  $ weight        : num  62 55.2 44 65.8 39.9 ...
##  $ allocation    : chr  "Control" "Control" "Control" "Control" ...
##  $ sbp_pre       : num  139 114 138 151 152 152 147 140 132 135 ...
##  $ sbp_post      : num  138 109 138 149 151 148 148 136 130 136 ...
##  $ gender_err    : num  0 1 1 1 1 0 1 1 2 1 ...
##  $ age_err       : num  85 72 61 65 54 72 72 79 72 77 ...
##  $ weight_err    : chr  "62,04" "55,23" "44,03" "65,76" ...
##  $ height_err    : chr  "163" "162" "176" "166" ...

Formattering af numeriske værdier

Talvariable bliver kun sjældent indlæst forkert. Men der kan være tilfælde, hvor man vil tvinge en variabel til at være tal eller heltal, hvilket gøres med funktionerne as.numeric() og as.integer():

# Formattering af numeriske værdier
testData$age <- as.numeric(testDataRaw$age)

# Formattering af heltal
testData$age <- as.integer(testDataRaw$age)

Formattering af tekst

Variable, som indeholder andet end tal vil som udgangspunkt blive formatteret som character, men ellers kan den tvinges med funktionen as.character():

# Formattering af tekst
testData$hospital <- as.character(testDataRaw$hospital)
##   [1] "2" "4" "3" "1" "4" "3" "3" "3" "5" "2" "5" "4" "3" "1" "4" "2" "1" "5"
##  [19] "5" "2" "4" "5" "4" "1" "2" "5" "5" "3" "2" "2" "3" "3" "4" "5" "4" "2"
##  [37] "5" "2" "2" "1" "1" "1" "1" "1" "2" "4" "4" "4" "2" "4" "4" "3" "5" "5"
##  [55] "1" "2" "1" "5" "4" "2" "5" "2" "3" "1" "4" "1" "3" "4" "2" "5" "1" "3"
##  [73] "1" "4" "4" "5" "5" "1" "5" "3" "3" "3" "2" "3" "5" "4" "5" "5" "3" "1"
##  [91] "3" "1" "3" "2" "1" "2" "4" "2" "3" "1"

Formattering af kategoriske data (factors)

Nogle typer af analyser virker kun med kategoriske variable, som i R kaldes for factors. Det er derfor vigtigt at kategoriske variable bliver formatteret korrekt. (Bemærk at man både kan bruge funktionerne as.factor() og factor())

Når man indlæser data med read.csv() sker denne formattering ikke automatisk (hvis data er tal indlæses de som numeric og hvis data er ord indlæses de som character). Nogle funktioner har en indbygget funktion, der kan få den til at indlæse tekst-variable som factors med argumentet stringsAsFactors - der er dog lidt delte meninger om det er en god ide, fordi R ‘gætter’ sig til om en variabel er en factor, hvilket kan påvirke reproducerbarheden. Det mest sikre er at indlæse variablen som character og derefter formattere til factor, så hele arbejdesgangen er dokumenteret.

I eksemplet indlæses variablen gender som en character variabel og skal derefter formatteres som factor med de to niveauer “M” og “F”. Når en character-variabel formatteres som factor vil alle unikke værdier blive lavet til en kategori og automatisk blive tildelt en tilsvarende label.

# Formattering af factor fra tekstvariabel
testData$gender <- as.factor(testDataRaw$gender)
##   [1] F M M F F M F M M M F F F M M M F M M F F F F F F F M M M M M F F M M M F
##  [38] M F M M M M M F F F M M M F F F M F M F F M M F F M M F F F M F F F F M F
##  [75] M F M F M M F M M F F F F M M F F F F M F M M M F M
## Levels: F M

Hvis variablen i csv-filen er indtastet som tal vil den blive indlæst som numeric eller integer. Disse kan sagtens formatteres som factor, hvor tallene så bliver brugt som labels

# Formattering af factor fra heltalvariabel
testData$hospital <- as.factor(testDataRaw$hospital)
##   [1] 2 4 3 1 4 3 3 3 5 2 5 4 3 1 4 2 1 5 5 2 4 5 4 1 2 5 5 3 2 2 3 3 4 5 4 2 5
##  [38] 2 2 1 1 1 1 1 2 4 4 4 2 4 4 3 5 5 1 2 1 5 4 2 5 2 3 1 4 1 3 4 2 5 1 3 1 4
##  [75] 4 5 5 1 5 3 3 3 2 3 5 4 5 5 3 1 3 1 3 2 1 2 4 2 3 1
## Levels: 1 2 3 4 5

Alternativt kan man tildele labels ved at tilføje argumenterne levels og labels - bemærk at man tilføjer flere værdier med funktionen c(). Levels argumentet definerer rækkefølgen af niveauerne og labels definerer navnene på niveauerne i samme rækkefølge:

# Formattering af factor fra heltalvariabel
testData$hospital <- as.factor(testDataRaw$hospital, 
                            levels = c(1,2,3,4,5), 
                            labels = c("NOH", "BBH", "HGH", 
                                       "AHH", "RH"))
##   [1] BBH AHH HGH NOH AHH HGH HGH HGH RH  BBH RH  AHH HGH NOH AHH BBH NOH RH 
##  [19] RH  BBH AHH RH  AHH NOH BBH RH  RH  HGH BBH BBH HGH HGH AHH RH  AHH BBH
##  [37] RH  BBH BBH NOH NOH NOH NOH NOH BBH AHH AHH AHH BBH AHH AHH HGH RH  RH 
##  [55] NOH BBH NOH RH  AHH BBH RH  BBH HGH NOH AHH NOH HGH AHH BBH RH  NOH HGH
##  [73] NOH AHH AHH RH  RH  NOH RH  HGH HGH HGH BBH HGH RH  AHH RH  RH  HGH NOH
##  [91] HGH NOH HGH BBH NOH BBH AHH BBH HGH NOH
## Levels: NOH BBH HGH AHH RH

Formattering af dato og tid

testData$admission_date <- as.Date(testDataRaw$admission_date)
##   [1] "2024-01-04" "2024-01-18" "2024-01-20" "2024-01-22" "2024-01-28"
##   [6] "2024-02-10" "2024-02-25" "2024-02-25" "2024-03-03" "2024-03-03"
##  [11] "2024-03-08" "2024-03-08" "2024-03-13" "2024-03-19" "2024-03-20"
##  [16] "2024-03-20" "2024-03-21" "2024-03-25" "2024-03-28" "2024-03-28"
##  [21] "2024-03-30" "2024-03-30" "2024-04-02" "2024-04-05" "2024-04-06"
##  [26] "2024-04-06" "2024-04-06" "2024-04-10" "2024-04-13" "2024-04-15"
##  [31] "2024-04-23" "2024-04-24" "2024-04-27" "2024-05-08" "2024-05-09"
##  [36] "2024-05-09" "2024-05-12" "2024-05-15" "2024-05-16" "2024-05-21"
##  [41] "2024-05-22" "2024-05-24" "2024-05-24" "2024-05-26" "2024-05-28"
##  [46] "2024-05-29" "2024-05-29" "2024-05-30" "2024-06-05" "2024-06-09"
##  [51] "2024-06-11" "2024-06-19" "2024-06-23" "2024-06-24" "2024-06-24"
##  [56] "2024-06-30" "2024-07-02" "2024-07-06" "2024-07-12" "2024-07-13"
##  [61] "2024-07-16" "2024-07-23" "2024-07-25" "2024-07-27" "2024-07-28"
##  [66] "2024-07-31" "2024-08-04" "2024-08-07" "2024-08-13" "2024-08-13"
##  [71] "2024-08-14" "2024-08-17" "2024-08-25" "2024-08-28" "2024-08-31"
##  [76] "2024-09-03" "2024-09-07" "2024-09-11" "2024-09-12" "2024-09-15"
##  [81] "2024-09-22" "2024-09-24" "2024-09-27" "2024-09-29" "2024-10-26"
##  [86] "2024-10-30" "2024-11-02" "2024-11-03" "2024-11-05" "2024-11-11"
##  [91] "2024-11-15" "2024-11-17" "2024-11-25" "2024-11-29" "2024-11-30"
##  [96] "2024-12-01" "2024-12-08" "2024-12-16" "2024-12-17" "2024-12-25"
testData$admission_date <- as.POSIXct(testDataRaw$admission_date, 
                                      format = "%Y-%m-%d")
##   [1] "2024-01-04 CET"  "2024-01-18 CET"  "2024-01-20 CET"  "2024-01-22 CET" 
##   [5] "2024-01-28 CET"  "2024-02-10 CET"  "2024-02-25 CET"  "2024-02-25 CET" 
##   [9] "2024-03-03 CET"  "2024-03-03 CET"  "2024-03-08 CET"  "2024-03-08 CET" 
##  [13] "2024-03-13 CET"  "2024-03-19 CET"  "2024-03-20 CET"  "2024-03-20 CET" 
##  [17] "2024-03-21 CET"  "2024-03-25 CET"  "2024-03-28 CET"  "2024-03-28 CET" 
##  [21] "2024-03-30 CET"  "2024-03-30 CET"  "2024-04-02 CEST" "2024-04-05 CEST"
##  [25] "2024-04-06 CEST" "2024-04-06 CEST" "2024-04-06 CEST" "2024-04-10 CEST"
##  [29] "2024-04-13 CEST" "2024-04-15 CEST" "2024-04-23 CEST" "2024-04-24 CEST"
##  [33] "2024-04-27 CEST" "2024-05-08 CEST" "2024-05-09 CEST" "2024-05-09 CEST"
##  [37] "2024-05-12 CEST" "2024-05-15 CEST" "2024-05-16 CEST" "2024-05-21 CEST"
##  [41] "2024-05-22 CEST" "2024-05-24 CEST" "2024-05-24 CEST" "2024-05-26 CEST"
##  [45] "2024-05-28 CEST" "2024-05-29 CEST" "2024-05-29 CEST" "2024-05-30 CEST"
##  [49] "2024-06-05 CEST" "2024-06-09 CEST" "2024-06-11 CEST" "2024-06-19 CEST"
##  [53] "2024-06-23 CEST" "2024-06-24 CEST" "2024-06-24 CEST" "2024-06-30 CEST"
##  [57] "2024-07-02 CEST" "2024-07-06 CEST" "2024-07-12 CEST" "2024-07-13 CEST"
##  [61] "2024-07-16 CEST" "2024-07-23 CEST" "2024-07-25 CEST" "2024-07-27 CEST"
##  [65] "2024-07-28 CEST" "2024-07-31 CEST" "2024-08-04 CEST" "2024-08-07 CEST"
##  [69] "2024-08-13 CEST" "2024-08-13 CEST" "2024-08-14 CEST" "2024-08-17 CEST"
##  [73] "2024-08-25 CEST" "2024-08-28 CEST" "2024-08-31 CEST" "2024-09-03 CEST"
##  [77] "2024-09-07 CEST" "2024-09-11 CEST" "2024-09-12 CEST" "2024-09-15 CEST"
##  [81] "2024-09-22 CEST" "2024-09-24 CEST" "2024-09-27 CEST" "2024-09-29 CEST"
##  [85] "2024-10-26 CEST" "2024-10-30 CET"  "2024-11-02 CET"  "2024-11-03 CET" 
##  [89] "2024-11-05 CET"  "2024-11-11 CET"  "2024-11-15 CET"  "2024-11-17 CET" 
##  [93] "2024-11-25 CET"  "2024-11-29 CET"  "2024-11-30 CET"  "2024-12-01 CET" 
##  [97] "2024-12-08 CET"  "2024-12-16 CET"  "2024-12-17 CET"  "2024-12-25 CET"
testData$admission <- as.POSIXct( paste( 
                          testDataRaw$admission_date, 
                          testDataRaw$admission_time), 
                          format = "%Y-%m-%d %H:%M")
##   [1] "2024-01-04 15:02:00 CET"  "2024-01-18 13:50:00 CET" 
##   [3] "2024-01-20 13:56:00 CET"  "2024-01-22 14:17:00 CET" 
##   [5] "2024-01-28 20:31:00 CET"  "2024-02-10 14:16:00 CET" 
##   [7] "2024-02-25 07:37:00 CET"  "2024-02-25 08:41:00 CET" 
##   [9] "2024-03-03 15:39:00 CET"  "2024-03-03 10:41:00 CET" 
##  [11] "2024-03-08 17:31:00 CET"  "2024-03-08 17:25:00 CET" 
##  [13] "2024-03-13 19:12:00 CET"  "2024-03-19 12:19:00 CET" 
##  [15] "2024-03-20 09:37:00 CET"  "2024-03-20 11:19:00 CET" 
##  [17] "2024-03-21 23:28:00 CET"  "2024-03-25 06:19:00 CET" 
##  [19] "2024-03-28 14:29:00 CET"  "2024-03-28 11:40:00 CET" 
##  [21] "2024-03-30 13:47:00 CET"  "2024-03-30 06:07:00 CET" 
##  [23] "2024-04-02 07:38:00 CEST" "2024-04-05 10:09:00 CEST"
##  [25] "2024-04-06 07:16:00 CEST" "2024-04-06 13:17:00 CEST"
##  [27] "2024-04-06 05:39:00 CEST" "2024-04-10 17:50:00 CEST"
##  [29] "2024-04-13 16:41:00 CEST" "2024-04-15 05:54:00 CEST"
##  [31] "2024-04-23 15:10:00 CEST" "2024-04-24 23:48:00 CEST"
##  [33] "2024-04-27 14:43:00 CEST" "2024-05-08 11:01:00 CEST"
##  [35] "2024-05-09 23:18:00 CEST" "2024-05-09 11:12:00 CEST"
##  [37] "2024-05-12 11:12:00 CEST" "2024-05-15 03:30:00 CEST"
##  [39] "2024-05-16 15:29:00 CEST" "2024-05-21 07:50:00 CEST"
##  [41] "2024-05-22 16:21:00 CEST" "2024-05-24 08:29:00 CEST"
##  [43] "2024-05-24 14:09:00 CEST" "2024-05-26 02:15:00 CEST"
##  [45] "2024-05-28 02:04:00 CEST" "2024-05-29 18:35:00 CEST"
##  [47] "2024-05-29 08:49:00 CEST" "2024-05-30 16:32:00 CEST"
##  [49] "2024-06-05 11:15:00 CEST" "2024-06-09 13:27:00 CEST"
##  [51] "2024-06-11 21:48:00 CEST" "2024-06-19 05:11:00 CEST"
##  [53] "2024-06-23 07:52:00 CEST" "2024-06-24 09:27:00 CEST"
##  [55] "2024-06-24 10:01:00 CEST" "2024-06-30 13:49:00 CEST"
##  [57] "2024-07-02 20:11:00 CEST" "2024-07-06 06:46:00 CEST"
##  [59] "2024-07-12 22:50:00 CEST" "2024-07-13 10:52:00 CEST"
##  [61] "2024-07-16 16:05:00 CEST" "2024-07-23 07:15:00 CEST"
##  [63] "2024-07-25 08:57:00 CEST" "2024-07-27 15:14:00 CEST"
##  [65] "2024-07-28 00:18:00 CEST" "2024-07-31 11:58:00 CEST"
##  [67] "2024-08-04 09:02:00 CEST" "2024-08-07 10:45:00 CEST"
##  [69] "2024-08-13 20:32:00 CEST" "2024-08-13 07:55:00 CEST"
##  [71] "2024-08-14 21:32:00 CEST" "2024-08-17 15:47:00 CEST"
##  [73] "2024-08-25 15:10:00 CEST" "2024-08-28 18:35:00 CEST"
##  [75] "2024-08-31 08:55:00 CEST" "2024-09-03 07:43:00 CEST"
##  [77] "2024-09-07 08:44:00 CEST" "2024-09-11 15:09:00 CEST"
##  [79] "2024-09-12 07:41:00 CEST" "2024-09-15 05:23:00 CEST"
##  [81] "2024-09-22 23:27:00 CEST" "2024-09-24 06:38:00 CEST"
##  [83] "2024-09-27 12:17:00 CEST" "2024-09-29 10:16:00 CEST"
##  [85] "2024-10-26 04:19:00 CEST" "2024-10-30 13:58:00 CET" 
##  [87] "2024-11-02 16:08:00 CET"  "2024-11-03 11:52:00 CET" 
##  [89] "2024-11-05 07:10:00 CET"  "2024-11-11 09:43:00 CET" 
##  [91] "2024-11-15 01:07:00 CET"  "2024-11-17 13:49:00 CET" 
##  [93] "2024-11-25 09:04:00 CET"  "2024-11-29 13:28:00 CET" 
##  [95] "2024-11-30 10:49:00 CET"  "2024-12-01 16:05:00 CET" 
##  [97] "2024-12-08 12:09:00 CET"  "2024-12-16 01:25:00 CET" 
##  [99] "2024-12-17 12:08:00 CET"  "2024-12-25 11:10:00 CET"
testData$admission <- lubridate::ymd_hm( paste(testDataRaw$admission_date, testDataRaw$admission_time))
##   [1] "2024-01-04 15:02:00 UTC" "2024-01-18 13:50:00 UTC"
##   [3] "2024-01-20 13:56:00 UTC" "2024-01-22 14:17:00 UTC"
##   [5] "2024-01-28 20:31:00 UTC" "2024-02-10 14:16:00 UTC"
##   [7] "2024-02-25 07:37:00 UTC" "2024-02-25 08:41:00 UTC"
##   [9] "2024-03-03 15:39:00 UTC" "2024-03-03 10:41:00 UTC"
##  [11] "2024-03-08 17:31:00 UTC" "2024-03-08 17:25:00 UTC"
##  [13] "2024-03-13 19:12:00 UTC" "2024-03-19 12:19:00 UTC"
##  [15] "2024-03-20 09:37:00 UTC" "2024-03-20 11:19:00 UTC"
##  [17] "2024-03-21 23:28:00 UTC" "2024-03-25 06:19:00 UTC"
##  [19] "2024-03-28 14:29:00 UTC" "2024-03-28 11:40:00 UTC"
##  [21] "2024-03-30 13:47:00 UTC" "2024-03-30 06:07:00 UTC"
##  [23] "2024-04-02 07:38:00 UTC" "2024-04-05 10:09:00 UTC"
##  [25] "2024-04-06 07:16:00 UTC" "2024-04-06 13:17:00 UTC"
##  [27] "2024-04-06 05:39:00 UTC" "2024-04-10 17:50:00 UTC"
##  [29] "2024-04-13 16:41:00 UTC" "2024-04-15 05:54:00 UTC"
##  [31] "2024-04-23 15:10:00 UTC" "2024-04-24 23:48:00 UTC"
##  [33] "2024-04-27 14:43:00 UTC" "2024-05-08 11:01:00 UTC"
##  [35] "2024-05-09 23:18:00 UTC" "2024-05-09 11:12:00 UTC"
##  [37] "2024-05-12 11:12:00 UTC" "2024-05-15 03:30:00 UTC"
##  [39] "2024-05-16 15:29:00 UTC" "2024-05-21 07:50:00 UTC"
##  [41] "2024-05-22 16:21:00 UTC" "2024-05-24 08:29:00 UTC"
##  [43] "2024-05-24 14:09:00 UTC" "2024-05-26 02:15:00 UTC"
##  [45] "2024-05-28 02:04:00 UTC" "2024-05-29 18:35:00 UTC"
##  [47] "2024-05-29 08:49:00 UTC" "2024-05-30 16:32:00 UTC"
##  [49] "2024-06-05 11:15:00 UTC" "2024-06-09 13:27:00 UTC"
##  [51] "2024-06-11 21:48:00 UTC" "2024-06-19 05:11:00 UTC"
##  [53] "2024-06-23 07:52:00 UTC" "2024-06-24 09:27:00 UTC"
##  [55] "2024-06-24 10:01:00 UTC" "2024-06-30 13:49:00 UTC"
##  [57] "2024-07-02 20:11:00 UTC" "2024-07-06 06:46:00 UTC"
##  [59] "2024-07-12 22:50:00 UTC" "2024-07-13 10:52:00 UTC"
##  [61] "2024-07-16 16:05:00 UTC" "2024-07-23 07:15:00 UTC"
##  [63] "2024-07-25 08:57:00 UTC" "2024-07-27 15:14:00 UTC"
##  [65] "2024-07-28 00:18:00 UTC" "2024-07-31 11:58:00 UTC"
##  [67] "2024-08-04 09:02:00 UTC" "2024-08-07 10:45:00 UTC"
##  [69] "2024-08-13 20:32:00 UTC" "2024-08-13 07:55:00 UTC"
##  [71] "2024-08-14 21:32:00 UTC" "2024-08-17 15:47:00 UTC"
##  [73] "2024-08-25 15:10:00 UTC" "2024-08-28 18:35:00 UTC"
##  [75] "2024-08-31 08:55:00 UTC" "2024-09-03 07:43:00 UTC"
##  [77] "2024-09-07 08:44:00 UTC" "2024-09-11 15:09:00 UTC"
##  [79] "2024-09-12 07:41:00 UTC" "2024-09-15 05:23:00 UTC"
##  [81] "2024-09-22 23:27:00 UTC" "2024-09-24 06:38:00 UTC"
##  [83] "2024-09-27 12:17:00 UTC" "2024-09-29 10:16:00 UTC"
##  [85] "2024-10-26 04:19:00 UTC" "2024-10-30 13:58:00 UTC"
##  [87] "2024-11-02 16:08:00 UTC" "2024-11-03 11:52:00 UTC"
##  [89] "2024-11-05 07:10:00 UTC" "2024-11-11 09:43:00 UTC"
##  [91] "2024-11-15 01:07:00 UTC" "2024-11-17 13:49:00 UTC"
##  [93] "2024-11-25 09:04:00 UTC" "2024-11-29 13:28:00 UTC"
##  [95] "2024-11-30 10:49:00 UTC" "2024-12-01 16:05:00 UTC"
##  [97] "2024-12-08 12:09:00 UTC" "2024-12-16 01:25:00 UTC"
##  [99] "2024-12-17 12:08:00 UTC" "2024-12-25 11:10:00 UTC"

Formattering af hele datasættet

Formatteringen af hele datasættet vil så se således ud:

testData <- data.frame(id = 1:length(testDataRaw$ id)) # Løbenummer
testData$ hospital <- factor(testDataRaw$hospital, 
                            levels = c(1,2,3,4,5), 
                            labels = c("NOH", "BBH", "HGH", 
                                       "AHH", "RH"))
testData$ admission <- lubridate::ymd_hm( paste(testDataRaw$admission_date, 
                                     testDataRaw$admission_time))
testData$ discharge <- lubridate::ymd_hm( paste(testDataRaw$discharge_date, 
                                     testDataRaw$discharge_time))
testData$ gender <- factor(testDataRaw$gender)
testData$ age <- testDataRaw$age
testData$ height <- testDataRaw$height
testData$ weight <- testDataRaw$weight
testData$ allocation <- factor(testDataRaw$allocation)
testData$ sbp_pre <- testDataRaw$sbp_pre
testData$ sbp_post <- testDataRaw$sbp_post

str(testData)
## 'data.frame':    100 obs. of  11 variables:
##  $ id        : int  1 2 3 4 5 6 7 8 9 10 ...
##  $ hospital  : Factor w/ 5 levels "NOH","BBH","HGH",..: 2 4 3 1 4 3 3 3 5 2 ...
##  $ admission : POSIXct, format: "2024-01-04 15:02:00" "2024-01-18 13:50:00" ...
##  $ discharge : POSIXct, format: "2024-01-12 14:23:00" "2024-01-25 09:31:00" ...
##  $ gender    : Factor w/ 2 levels "F","M": 1 2 2 1 1 2 1 2 2 2 ...
##  $ age       : num  85 72 61 65 54 72 72 79 72 77 ...
##  $ height    : num  163 162 176 166 167 180 174 175 183 170 ...
##  $ weight    : num  62 55.2 44 65.8 39.9 ...
##  $ allocation: Factor w/ 2 levels "Control","Treatment": 1 1 1 1 1 2 1 2 1 1 ...
##  $ sbp_pre   : num  139 114 138 151 152 152 147 140 132 135 ...
##  $ sbp_post  : num  138 109 138 149 151 148 148 136 130 136 ...

Undersøgelse og rettelse af fejl

Hvis data indeholder fejl eller ikke er formatteret korrekt vil der være risiko for, at resultaterne af ens analyser bliver forkerte. Det er derfor vigtigt at gennemgå alle variable, så man er sikker på at alt er korrekt.

Fejl kan både opstå ved indlæsningen i af data i R (det er typisk kun formatteringsfejl), men ellers vil mange fejl være indtastningsfejl eller fejl der opstår i senere håndtering af data (fx er Excel kendt for automatisk at formattere data til andre formatter - fx vil den lave alt der minder om datoer om til datoer).

Fejlindtastning i numeriske data

I numeriske variable vil den typiske fejl være indtastningsfejl. Mindre fejl kan være svære at se, men vil tilgengæld ikke påvirke analyser i særlig høj grad (det har lille betydning om man har tastet 66 i stedet for 68). Til gengæld kan ekstreme værdier have stor betydning for resultatet af ens analyser. I de fleste forskningsdatabaser vil man have defineret grænse for hvad der kan indtastes, som forhindre de fleste ekstreme værdier. Men hvis man skal arbejde med lidt ældre data skal man være mere påpasselig. Noget man også skal være opmærksom på med ældre data er at databaserne ofte ikke kunne håndterer andre datatyper end det definerede - derfor var det normalt at man indtastede ‘ukendte’ værdier som en talværdier uden for det mulige spektrum - ofte enten 9, 99, 999 eller 9999.

Deskriptiv statistik

En måde at undersøge for ekstreme værdier er med funktionen summmary(), som returnere de mest almindelige deskriptive estimater:

summary(testDataRaw$age_err)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   40.00   64.00   71.00   97.99   77.00  999.00

Her kan vi både se at den maksimale værdi i variablen ‘age’ er 999, samt at gennemsnittet er betydeligt højere end medianen.

Visualisering

En anden måde man kan undersøge for ekstreme værdier er at tegne et plot:

plot(testDataRaw$age_err)

Her bliver det tydeligt at der er tre værdier på 999 - og som altså afviger betydeligt fra resten af data.

Rettelse af de ekstreme værdier

Når man opdager ekstreme værdier, må man overveje om de potentielt kunne være sande - og derfor skal blive i datasættet - eller om de skal fjernes. I nogle tilfælde kan man prøve at finde den sande værdier fra andre kilder. Men ofte vil den eneste mulighed være, at fjerne værdier ved at udskifte dem med NA. En metode er at bruge funktionen ifelse(b, T, F), hvor b er en betingelse, der testes; T er handlingen hvis betingelsen er sand og F er handlingen hvis betingelsen er falsk. I eksemplet testes om værdier ikke er 999 (!= betyder ‘ikke lig med’); og hvis betingelsen er sand bevares værdien, men hvis betingelsen er falsk indsættes NA:

testData$age_err <- ifelse(testDataRaw$age_err != 999, testDataRaw$age_err, NA)
##   [1]  85  72  61  65  54  72  72  79  72  77  64  64  68  87  68  73  59  73
##  [19]  84  67  81  62  62  63  55  64  89  68  NA  71  67  73  62  81  59  78
##  [37]  72  71  77  61  58  67  82  76  93  60  71 101  70  79  78  86  71  82
##  [55]  53  62  71  64  72  53  NA  65  65  68  64  53  76  70  40  81  63  72
##  [73]  73  59  55  85  89  76  72  71  73  NA  80  77  64  71  70  71  80  65
##  [91]  76  72  70  80  71  59  57  67  63  58

Fejl i kategorier

Unique

Det første vi kan undersøge er hvor mange unikke værdier variablen har. Umiddelbart vil vi kun forvente, at variablen ‘gender’ har to mulige værdier - med mindre man har givet mulighed for yderligere muligheder, men i så fald vil der være meget få af dem. Hvis man skal undersøge hvilke unikke værdier variablen har bruger man funktionen unique():

unique(testDataRaw$gender_err)
## [1] 0 1 2

Her kan man se at der er tre unikke værdier.

Derefter kan man bruge funktionen table() til at undersøge antallet af hver værdi:

table(testDataRaw$gender_err)
## 
##  0  1  2 
## 29 63  8

Her kan vi se at der er en lidt påfaldende fordeling af værdier. Selv hvis der var en mulighed, som hed ‘Andet’ ville man kun forvente få besvarelser i denne værdi. En anden mulighed kunne være, at data ikke er blevet kodet ens i hele datasættet. Det kan nogengange forekomme, når man samkører data indsamlet fra forskellige kilder (fx hvis der rekrutteres deltagere fra forskellige behandlingssteder).

Derfor kan vi prøve at opdele efter behandlingssted:

table(testDataRaw$gender_err, testDataRaw$hospital)
##    
##      1  2  3  4  5
##   0  8  8  5  8  0
##   1 12 12 15 12 12
##   2  0  0  0  0  8

Her kan man se at fire af behandlingsstederne har kodet køn som 0/1, mens et enkelt behandlingssted tilsyneladende har kodet variablen som 1/2. Her kan vi igen bruge funktionen ifelse() til at rette de fejlagtige værdier, men bevare de korrekte værdier. Men i dette tilfælde bruges yderligere en ifelse() til at rette påvirkede værdier:

testData$gender_err2 <- ifelse(testDataRaw$hospital == 5, 
                               ifelse(testDataRaw$gender_err == 1, 0, 1) , 
                               testDataRaw$gender_err)
table(testDataRaw$gender_err2, testDataRaw$hospital)
##    
##      1  2  3  4  5
##   0  8  8  5  8 12
##   1 12 12 15 12  8

Fejl i decimaltegn

En fejl man nogle gange kommer til er at importere en numerisk variabel, hvor der er brugt kommer som decimal-tegn. R forventer i per default at der bruges punktum - så hvis det ikke specificeres at der er komma - vil R fortolke variablen som en tekst variabel.

testDataRaw$ weight_err
##   [1] "62,04"  "55,23"  "44,03"  "65,76"  "39,85"  "76,87"  "65"     "80,54" 
##   [9] "79,32"  "63,22"  "67,78"  "70,99"  "77,44"  "74,76"  "86,23"  "80,57" 
##  [17] "91,48"  "44,81"  "60,17"  "64,82"  "71,47"  "92,29"  "57,92"  "60,64" 
##  [25] "56,2"   "85,14"  "82,63"  "46,6"   "77,45"  "95,8"   "92,66"  "73,26" 
##  [33] "51,66"  "53,28"  "75,04"  "85,57"  "61,17"  "97,88"  "70,68"  "65,34" 
##  [41] "66,76"  "67,09"  "59,32"  "89,62"  "53,94"  "68,81"  "83,77"  "100,32"
##  [49] "91,84"  "81,23"  "62,58"  "59,52"  "46,18"  "102,72" "69,16"  "84,74" 
##  [57] "81,02"  "66,79"  "91,59"  "89,86"  "71,99"  "79,13"  "92,99"  "96,42" 
##  [65] "92,92"  "81,12"  "51,67"  "91,93"  "58,35"  "78,23"  "88,5"   "67,98" 
##  [73] "58,39"  "66,87"  "85,9"   "59,29"  "71,54"  "60,14"  "83,99"  "89,72" 
##  [81] "105,98" "79,69"  "70,14"  "73,08"  "38,79"  "71,63"  "97,26"  "46,18" 
##  [89] "99,33"  "89,55"  "67,96"  "59,24"  "69,99"  "88,47"  "77,52"  "80,35" 
##  [97] "84,63"  "70,03"  "74,5"   "94,45"

Hvis man prøver at lave den om til tal med funktionen as.numeric() vil den returnere NA værdier:

testData$ weight_err <- as.numeric(testDataRaw$ weight_err)
## Warning: NAs introduced by coercion

En løsning vil være at importere variablen igen med en specifikation om at decimal-tegnet er “,”.

Men man kan også rette variablen ved at udskifte kommaerne med punktumer - hvilket kan gøres med funktionen gsub():

testData$ weight_err <- as.numeric( gsub(",",".", testDataRaw$ weight_err) )

Eller med funktionen str_replace() fra pakken {stringr}:

testData$ weight_err <- as.numeric(
                      stringr::str_replace(testDataRaw$ weight_err, ",", "."))

Begge funktioner retter komma til punktum, som om det var et stykke tekst - men det gør at R efterfølgende fortolker tallet korrekt, når man bruger as.numeric().

Fejl i NA-værdier

Nogle gange få man data, hvor manglende værdier er kodet som noget andet end NA - fx med et punktum eller teksten “Missing value”. Her vil R i første omgang importere data som character variabel.

unique(testDataRaw$ height_err)
##  [1] "163" "162" "176" "166" "167" "180" "."   "175" "183" "170" "173" "197"
## [13] "184" "172" "154" "169" "179" "168" "171" "189" "191" "185" "188" "177"
## [25] "186" "194" "165" "195" "157" "201" "181" "161" "159" "203" "153" "193"
## [37] "178" "182"

I de fleste tilfælde kan man tvinge variablen til at blive tal med as.numeric() - og så vil R automatisk lave alle værdier, som ikke er tal, om til NA:

testData$ height_err <- as.numeric(testDataRaw$ height_err)
## Warning: NAs introduced by coercion
##   [1] 163 162 176 166 167 180  NA 175 183 170  NA  NA  NA 173 197 184 172 154
##  [19]  NA 169 179 170  NA 168  NA 172 171 171 183 189 197 173 166 166 171 191
##  [37] 163 185 167 188 179 173 173 180 177 186 186 194 170 186 165 166 172 195
##  [55] 157 201 168 165 188 177 181  NA 183 180 177 169  NA 186  NA  NA  NA  NA
##  [73]  NA 161 191 159 179  NA  NA  NA 203 189 168 179 153 168 180 157  NA  NA
##  [91] 162 168 193 173 171 178 182 197 162  NA

Test data

Eksemplerne i denne guide er baseret på et simuleret datasæt med tilfældige værdier.

Herunder kan man se koden der er brugt til at lave datasættet.

# Generering af test datasæt
set.seed(123) # Seed for randomisering
n <- 100 # Antal observationer
testDataRaw <- data.frame(id = 1:n) # Observations id
# Tilfældig trækning af 1 til 5; replace = FALSE sikre at der er lige mange 
# hver gruppe
testDataRaw$ hospital <- sample(c(rep(1,n/5),rep(2,n/5),rep(3,n/5),
                                  rep(4,n/5),rep(5,n/5)), n, replace = FALSE)
# Tilfælde datoer i 2024; sort() gør at datoer er kontinuerlige
testDataRaw$ admission_date <- sort( as.Date(
        runif(n, min = as.numeric(as.Date("2024-01-01")), 
        max = as.numeric(as.Date("2024-12-31"))), 
        origin = "1970-01-01"))
# Tilfældigt indlæggelsestidspunkt 
# Timerne er normalfordelt omkring kl 11
testDataRaw$ admission_time <- format(as.POSIXct(
  paste0((round( rnorm(n, mean = 11, sd = 5)) %% 24),":",
         sample(0:59,n,replace = TRUE)), format="%H:%M"), "%H:%M")
# Udskrivelsesdatoen er indlæggelsesdatoen plus et tilfældigt antal dage
testDataRaw$ discharge_date <- testDataRaw$ admission_date + 
                                sample(2:10, 100, replace = TRUE)
# Datoerne formateres som character i stedet for posix
testDataRaw$ admission_date <- as.character(testDataRaw$ admission_date)
testDataRaw$ discharge_date <- as.character(testDataRaw$ discharge_date)
# Udskrivelses tidspunktet er tilfældigt efter en normalfordeling omkring kl 14
testDataRaw$ discharge_time <- format(as.POSIXct(
  paste0((round( rnorm(n, mean = 14, sd = 2)) %% 24),":",
         sample(0:59, n, replace = TRUE)), format="%H:%M"), "%H:%M")
# Tilfældigt køn
testDataRaw$ gender <- sample(c("F","M"), n, replace = TRUE)
# Tilfældig alder | normalfordelt omkring 70 år
testDataRaw$ age <- round( rnorm(n, mean = 70, sd = 10) )
# Tilfældig højde betinget af køn, så mændene har en gennemsnitshøjde på 180 cm 
# og kvinder en gennemsnitshøjde på 170 cm
testDataRaw$ height <- unlist(lapply(testDataRaw$ gender, 
                          function(x){if(x == "M"){
                            return(round( rnorm(1, mean = 180, sd = 10) ))} 
                          else {
                            return(round( rnorm(1, mean = 170, sd = 10) ))} 
                            }))
# Vægt udregnes ud fra højde med et tilfældigt BMI; BMI er udregnet som en
# kvadradisk funktion af en normalfordeling med mean 600 (BMI = ca 24.5)
testDataRaw$ weight <- sqrt(rnorm(n, mean = 600, sd = 180)
                            )*(testDataRaw$ height/100)^2
# Tilfældig allokering til behandling eller kontrol
testDataRaw$ allocation <- sample(c("Treatment","Control"), n, replace = TRUE)
# Tilfældigt normalfordelt blodtryk før behandling
testDataRaw$ sbp_pre <- round( rnorm(n, mean = 140, sd = 10) )
# Tilfældigt normalfordelt blodtryk efter behandling hvor en betingelse gør at
# "Treatment" har et gennemsnitligt fald i blodtryk og "Control" er uændret
testDataRaw$ sbp_post <- testDataRaw$ sbp_pre+unlist(
                          lapply(testDataRaw$ allocation, 
                          function(x){if(x == "Treatment"){
                            return(round( rnorm(1, mean = -5, sd = 3) ))} 
                          else {
                            return(round( rnorm(1, mean = 0, sd = 2) ))} }))
# Køns-variabel hvor en betingelse laver en anden indtastning hvis "hospital" 
# er lig med 5
testDataRaw$ gender_err <- unlist(lapply(testDataRaw$ hospital, 
                          function(x){if(x == 5){
                            return(sample(c(1,2), 1, replace = TRUE))} 
                          else {
                            return(sample(c(0,1), 1, replace = TRUE))} }))
# Alders-variabel hvor 3 tilfældige værdier udskiftes med værdien 999
testDataRaw$ age_err <- testDataRaw$age
testDataRaw$ age_err[sample(1:n, 3, replace = FALSE)] = 999
# Vægt-variabel hvor decimal-tegnet udskiftes med komma pg formateres til tekst
testDataRaw$ weight_err <- stringr::str_replace(as.character(
                            round(testDataRaw$ weight,2)),"[.]", ",")
# Højde-variabel hvor 20 tilfældige værdier udskiftes med punktum og formateres
# som tekst
testDataRaw$ height_err <- as.character(testDataRaw$ height)
testDataRaw$ height_err[sample(1:n, 20, replace = FALSE)] = "."

Forklaring på de anvendte funktioner

Sekvenser af tal kan laves på to måder enten ved angive starttaller og sluttallet, adskilt af et enkelt kolon, fx vil koden 1:10 give en vektor fra 1 til 10:

1:10
##  [1]  1  2  3  4  5  6  7  8  9 10

Funktionen seq(min, max) giver samme output:

seq(1, 10)
##  [1]  1  2  3  4  5  6  7  8  9 10

Hvis man vil have en sekvens med større spring, fx 10, bruges koden seq(min, max, by), hvor argumentet by angiver størrelsen på springet:

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

Funktionen rep(x, n) (replicate) bruges til at lave en vektor med en bestemt værdi et antal gange (x angiver værdien og n angiver antallet af gentagelser):

rep(1,10)
##  [1] 1 1 1 1 1 1 1 1 1 1

Funktionen sample(x, n, replace) bruges til at lave tilfældige trækninger af en vektor (x), n antal gange, enten med eller uden tilbagelægning (replace defineres som entel TRUE eller FALSE, hvor default er FALSE). Hvis replace defineres som FALSE skal vektoren være mindst samme længde som n.

sample(c("Treatment","Control"), 10, replace = TRUE)
##  [1] "Treatment" "Treatment" "Control"   "Treatment" "Control"   "Treatment"
##  [7] "Treatment" "Control"   "Treatment" "Treatment"

Funktionen rnorm(n, mean, sd) bruges til at lave en vektor med tilfældige værdier efter en normalfordeling, hvor n angiver antallet af trækninger, mean angiver gennemsnittet for normalfordelingen og sd angiver standarddeviationen for normalfordelingen:

rnorm(10, mean = 70, sd = 10)
##  [1] 68.04207 75.64337 82.57325 63.65407 74.24233 71.80031 67.70405 69.26792
##  [9] 63.17828 69.15111

Funktionen runif(n, min, max) bruges til at lave en vektor med tilfældige værdier mellem to værdier, hvor n angiver antallet af trækninger, min angiver den laveste værdi og max angiver den højeste værdi. Trækningen har ingen fordeling og alle værdier har derfor samme sandsynlighed:

runif(10, min = 0, max = 100)
##  [1] 94.3224738 37.4545655  0.8041536 98.0524503 37.7540056 98.5867555
##  [7] 13.9616391 56.2218103 79.4360194 19.3901369

Funktionen lapply(x, FUN) bruges til at anvende en funktion på en liste af værdier, hvor x angiver listen og FUN angiver funktionen, der skal anvendes (Bruges på samme måde som et for-loop). I eksemplet er x en sekvens fra 1 til 10 og FUN er en funktion, som returnerer kvadrattallet af x:

lapply(1:10, function(x){x^2})
## [[1]]
## [1] 1
## 
## [[2]]
## [1] 4
## 
## [[3]]
## [1] 9
## 
## [[4]]
## [1] 16
## 
## [[5]]
## [1] 25
## 
## [[6]]
## [1] 36
## 
## [[7]]
## [1] 49
## 
## [[8]]
## [1] 64
## 
## [[9]]
## [1] 81
## 
## [[10]]
## [1] 100

Selvom lapply() tager en vektor som input vil den returnere en liste af resultater, som ikke har den samme datastruktur som inputtet. Hvis man skal bruge lapply() på en variabel i en data.frame til at transformere til en ny variabel, skal man bruge funktionen unlist() til at lave outputtet til en vektor:

unlist( lapply(1:10, function(x){x^2}) )
##  [1]   1   4   9  16  25  36  49  64  81 100

I datasimulationen bruges lapply() til at lave variabel med højde, som er betinget af køn. Mænd (gender == "M") gives en højde en gennemsnit på 180 cm og en standarddeviation på 10 cm, mens kvinder gives en højde med et gennemsnit på 170 cm og en standarddeviation på 10 cm:

testDataRaw$ height <- unlist(lapply(testDataRaw$ gender, 
                          function(x){if(x == "M"){
                            return(round( rnorm(1, mean = 180, sd = 10) ))} 
                          else {
                            return(round( rnorm(1, mean = 170, sd = 10) ))} 
                            }))
id gender height
1 F 176
2 M 157
3 M 179
4 F 196
5 F 171
6 M 193
7 F 173
8 M 194
9 M 175
10 M 184
11 F 169
12 F 175
13 F 147
14 M 167
15 M 178

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/

QR-kode til denne side: