Funktioner: oversigt

Hvad hver enkelt funktion gør - forklaret til dem, der aldrig har kodet før

Published

July 2, 2026

Kender du funktionens navn? Brug Ctrl+F (Windows) eller Cmd+F (Mac) og søg direkte på siden.

NoteSpring direkte til
Emne Sektion
Røret %>% Røret
Indlæs register / åbn parquet-fil Dataindlæsning
Filtrer rækker Rækker: filtrer og deduplikér
Vælg eller tilføj kolonner Kolonner: vælg, tilføj, omdan
Omdøb kolonner Kolonnenavne
Grupper, tæl og aggregér Grupper og aggregering
Join to tabeller Join - sammensæt tabeller
Pivot (bredt ↔︎ langt format) Omformning
collect() og doven evaluering Doven forespørgsel
Datoer og tekst Tekstoperationer · Tal og datoer
Fejlsøgning Diagnostik og fejlsøgning
TipDe vigtigste funktioner - start her

Forstår du de 6 funktioner herunder, kan du læse de fleste linjer registerdata-kode.

Funktion Hvad den gør
open_dataset("sti") Åbner en doven forbindelse til et register (i DARTER: read_register("navn"))
filter(betingelse) Beholder kun de rækker, der opfylder betingelsen
select(kol1, kol2) Beholder kun de angivne kolonner
mutate(ny_kol = ...) Tilføjer eller ændrer en kolonne
left_join(anden_df, by = "nøgle") Joiner to tabeller på en fælles nøgle
collect() Henter data fra parquet ind i R-hukommelsen

Rækkefølgen er intentionel: open_datasetfilterselectcollect er det mønster, der optræder i næsten hvert script.


Hvad er en funktion?

Tænk på en funktion som en maskine på et samlebånd. Du sender noget ind i den ene ende, den gør noget ved det, og du får noget nyt ud i den anden ende. filter() er for eksempel en si: du sender en stor tabel ind, angiver hvilke rækker du vil beholde, og får en mindre tabel ud.

Alle funktioner i R skrives med parenteser: funktionsnavn(hvad_der_sendes_ind).

Det, du skriver inde i parentesen, kaldes argumenter. Mange funktioner har navngivne argumenter på formen navn = værdi: til venstre for = står argumentets navn (fastlagt af funktionen, kan ikke laves om), og til højre den værdi, du selv giver det, fx ratio = 5 eller na.rm = TRUE. Du behøver ikke kende dem udenad: ?funktionsnavn viser, hvilke argumenter en funktion tager, og hvad de gør.

Tip

Vil du se hvad en funktion gør? Sæt markøren midt i funktionsnavnet og tryk F1 - hjælpesiden åbner med beskrivelse, argumenter og eksempler direkte i RStudios Help-panel. Du kan også skrive ?funktionsnavn eller help(funktionsnavn) i konsollen, eller args(funktionsnavn) for en hurtig liste over argumenter.

For pakker på CRAN ligger der en online-side med dokumentation og vignetter (fx https://cran.r-project.org/package=MatchIt). Nogle DST-pakker (fx heaven) ligger på GitHub i stedet - brug ?funktionsnavn for argumenterne og bekræft pakkens tilgængelighed på dit eget projekt.


Røret - %>%

Det vigtigste symbol i al koden er %>%, “the pipe”.

lpr_adm %>%
  semi_join(tibble(pnr = mine_pnrs), by = "pnr") %>%
  select(pnr, recnum) %>%
  collect()

Hvad det gør: røret sender resultatet fra linjen til venstre videre som første argument til funktionen til højre.

De to skrivemåder gør det samme:

# Uden røret - indefra og ud, som russiske dukker:
collect(select(semi_join(lpr_adm, tibble(pnr = mine_pnrs), by = "pnr"), pnr, recnum))

# Med røret - oppefra og ned, som en opskrift:
lpr_adm %>%
  semi_join(tibble(pnr = mine_pnrs), by = "pnr") %>%
  select(pnr, recnum) %>%
  collect()

Begge versioner giver nøjagtigt det samme resultat. Pipe-versionen er lettere at læse fordi du kan følge trinene i rækkefølge - og lettere at fejlfinde fordi du kan tilføje eller fjerne ét trin ad gangen.

Tip

Linjeskift efter %>% er ikke et krav - kun for læsbarhed. Du kan skrive hele kæden på én linje (bef %>% filter(...) %>% collect()) eller dele den op med ét trin per linje. Den eneste regel: sætter du linjeskift, skal %>% stå i slutningen af linjen, ikke i starten af den næste. R læser linje for linje, så et %>% til sidst signalerer “der kommer mere”:

# Virker - %>% til sidst på linjen:
bef <- bef %>%
  filter(year == 2015) %>%
  collect()

# Fejler - R tror udtrykket sluttede efter "bef":
bef <- bef
  %>% filter(year == 2015)

Det samme gælder |> og + i ggplot2.

Anekdote: Forestil dig at du laver mad. Du skærer løgene - og sender dem videre til gryden - som sender indholdet videre til tallerkenen. Røret gør præcis det: det kæder trin sammen, så du kan læse koden oppefra og ned som en opskrift.

Note

%>% og |> er det samme - bare to forskellige måder at skrive røret på.

%>% kommer fra pakken magrittr og er tilgængeligt via library(dplyr). |> er en indbygget version introduceret i R 4.1 - den kræver ingen pakke.

De to virker identisk i næsten alle situationer. Du vil se begge i R-kode på nettet. Projektet bruger %>%, men hvis du skriver |> er det helt fint.


Dataindlæsning

open_dataset("sti") - arrow

Hvad det gør: åbner en doven forbindelse til en parquet-fil eller -mappe.

Anekdote: Forestil dig at du ringer til biblioteket og beder dem om at finde alle bøger om hjertekirurgi fra 1990 til 2020. Bibliotekaren siger “ja, dem finder jeg” - men de er ikke ankommet endnu. Det er præcis hvad open_dataset() gør: det fortæller computeren hvad du vil have, men data er ikke hentet ind i hukommelsen endnu. Resten af dine kommandoer (filter, select) tilføjer yderligere instruktioner, inden du til sidst siger “send dem nu” - det er collect().

library(fastreg)
bef <- read_register("bef") %>% rename_with(tolower)   # via navn - fastreg kender stien

read_register() finder stien fra din projekt-config (sat én gang) - se Fase 4. Den returnerer en DuckDB-forbindelse.

bef <- open_dataset("E:/workdata/[projektnummer]/cleaned-data/parquet-registers/bef/") %>%
  rename_with(tolower)   # standardisér kolonnenavne til lowercase

De bekræftede stier til dit projekt finder du i Register-overblik.

bef <- open_dataset("synth_data/bef/") %>% rename_with(tolower)   # sti til lokalt gemt syntetisk register

Generer og gem syntetiske data lokalt inden brug - se Fase 6 - Første udtræk.

Bruges til alle registre der er gemt som parquet-filer: bef, lpr_adm, lpr_diag, lmdb, dodsaars, vnds, udda, faik, akm, t_psyk_adm, t_psyk_diag, lpr_a_kontakt, lpr_a_diagnose med flere.

Note

Arrow eller DuckDB? open_dataset() giver dig en Arrow-forbindelse. Arrow er hurtig, men understøtter ikke alle dplyr-funktioner: fejler et trin med en “unsupported function”-fejl, løser det sig som regel ved at skifte til DuckDB, der understøtter næsten alle dplyr-verber. Send data gennem to_duckdb():

library(arrow)   # open_dataset, to_duckdb
bef <- open_dataset("sti/til/bef/") %>%
  to_duckdb() %>%          # giv data videre til DuckDB
  rename_with(tolower)

Det skal du ikke med read_register() (fastreg) - de giver dig allerede en DuckDB-forbindelse, så to_duckdb()-konverteringen er indbygget. Hvornår du bruger hvad, kan du læse i Fase 5 - Arrow vs. DuckDB.


collect()

Hvad det gør: udfører den dovne forespørgsel og trækker data ind i R-hukommelsen.

Det er det punkt, hvor bibliotekaren rent faktisk bringer bøgerne hen til dig. Kald det sent - efter alle filter() og select() trin - så kun de nødvendige data flyttes.

resultat <- stort_register %>%
  semi_join(tibble(pnr = kohort_pnrs), by = "pnr") %>%   # kun kohortens pnr'er
  select(pnr, d_inddto) %>%            # kun de to kolonner vi bruger
  collect()                             # data trækkes ind i R-hukommelsen
Note

Konceptet med doven evaluering - hvorfor data ikke er i hukommelsen før collect() - er forklaret grundigt på siden Udtræk trin for trin.


readRDS("sti/fil.rds")

Hvad det gør: læser en gemt R-fil fra disk ind i hukommelsen.

Anekdote: Det er som at åbne en Word-fil, du gemte sidst. .rds-formatet er R’s eget gemmingsformat - hurtigere og mere kompakt end CSV. Det bruges til at sende data videre fra ét script til det næste i pipeline’en.

full_cohort <- readRDS("sti/til/full_cohort.rds")   # hent gemt datasæt fra disk

saveRDS(objekt, "sti/fil.rds")

Hvad det gør: gemmer et R-objekt til disk.

Det modsatte af readRDS(). Hvert pipeline-script gemmer sit resultat med saveRDS(), så det næste script kan hente det.

saveRDS(full_cohort, "sti/til/full_cohort.rds")   # gem datasæt til disk

haven::read_sas("fil.sas7bdat")

Hvad det gør: læser en SAS-datafil ind i R.

Bruges til at indlæse SAS-filer - fx DST’s formateringstabeller eller registre der ikke er konverteret til parquet. Se Filformater og Formateringstabeller for praktiske eksempler.


arrow::write_parquet(df, "sti/fil.parquet")

Hvad det gør: gemmer en data frame som en parquet-fil.

Parquet er et særligt effektivt filformat til store datasæt - det er langt hurtigere at læse end CSV. Bruges når du vil gemme et R-datasæt som parquet-fil, fx til brug med open_dataset() eller read_register() i et andet script.


file.path(mappe, "filnavn.rds")

Hvad det gør: sætter en mappesti og et filnavn korrekt sammen til en fuld sti.

Anekdote: Tænk på det som at skrive en adresse: file.path("C:/data", "resultater.rds") giver "C:/data/resultater.rds". Det er sikrere end bare at klistre strenge sammen med paste0(), fordi det håndterer skråstreger korrekt på alle operativsystemer.


file.exists("sti")

Hvad det gør: returnerer TRUE eller FALSE - eksisterer filen?

Bruges til at give en forståelig fejlbesked, inden koden forsøger at åbne en fil, der måske ikke er der.


dir.create(sti, showWarnings = FALSE, recursive = TRUE)

Hvad det gør: opretter en mappe, hvis den ikke allerede eksisterer.

recursive = TRUE opretter også eventuelle overordnede mapper. showWarnings = FALSE undertrykker den harmløse advarsel, du ellers ville få, hvis mappen allerede fandtes.


Kolonnenavne

rename_with(tolower)

Hvad det gør: konverterer ALLE kolonnenavne til små bogstaver på én gang.

Anekdote: Forestil dig at du får udleveret en patientliste fra fem forskellige afdelinger. Den ene skriver “CPR”, en anden “cpr”, en tredje “Cpr”. Det er det samme - men computeren behandler dem som tre helt forskellige ting. rename_with(tolower) fikser det på ét sekund: alle navne bliver ensartede.

bef <- open_dataset("sti/til/bef") %>% rename_with(tolower)   # alle kolonner bliver lowercase: pnr, koen, foed_dag, ...

Vigtigt: kald det altid på samme linje som open_dataset() (eller read_register() i DARTER). Glemmer du det, vil semi_join(..., by = "pnr") fejle, fordi kolonnen måske hedder PNR.


rename(nyt_navn = gammelt_navn)

Hvad det gør: omdøber én eller flere specifikke kolonner.

rename(surgery_date = index_date)   # index_date omdøbes til surgery_date

names(df)

Hvad det gør: viser alle kolonnenavne i en data frame.

Nyttigt, når du ikke er sikker på, hvad en tabel indeholder.

names(full_cohort)   # udskriver alle kolonnenavne

Kolonner: vælg, tilføj, omdan

select(kol1, kol2, nyt = gammelt, -fjern)

Hvad det gør: beholder kun de kolonner, du nævner.

Anekdote: Du har en stor Excel-fil med 50 kolonner. Du skal kun bruge 3. select() er som at gemme et udsnit med kun de tre kolonner du har brug for. Det reducerer mængden af data, der trækkes fra serveren, og er en af grundene til at koden er hurtig.

select(pnr, recnum, dato = d_inddto)   # beholder tre kolonner; d_inddto omdøbes til dato
select(-aar)                            # fjerner kolonnen aar; beholder alle andre

Når du omdøber inde i select(), er retningen nyt_navn = gammelt_navn - det nye navn til venstre, det eksisterende kolonnenavn til højre.


mutate(ny_kolonne = udtryk)

Hvad det gør: tilføjer en ny kolonne (eller ændrer en eksisterende), beregnet ud fra de kolonner du allerede har. Antallet af rækker er det samme - mutate() tilføjer information, fjerner ikke rækker. (df er bare navnet på din data frame - kald den, hvad du vil.)

library(dplyr)

df <- df %>%
  mutate(
    bmi     = vaegt / (hoejde^2),   # ny kolonne beregnet fra to eksisterende
    obesity = bmi >= 30             # TRUE/FALSE - en nyligt lavet kolonne kan bruges med det samme
  )

Du kan lave flere variable i ét kald, og en variabel du lige har lavet, er straks tilgængelig længere nede (her bruger obesity_class den netop beregnede bmi):

df <- df %>%
  mutate(
    bmi = vaegt / (hoejde^2),
    obesity_class = case_when(       # case_when: flere betingelser; den FØRSTE sande bestemmer værdien
      bmi < 25 ~ "Normal",
      bmi < 30 ~ "Overvægt",
      TRUE     ~ "Svær overvægt"     # TRUE = "alt andet"
    )
  )
Warning

Tjek enhederne først. BMI = vægt (kg) / højde (m)². Er højden i cm, bliver vaegt / (hoejde^2) forkert. Lav højden om til meter i et mutate()-trin, før du beregner BMI:

df <- df %>%
  mutate(hoejde_m = hoejde_cm / 100) %>%   # cm -> m FØRST
  mutate(bmi = vaegt / (hoejde_m^2))       # ... så er BMI korrekt

case_when() har sit eget opslag nedenfor. Flere eksempler på mutate():

mutate(icd3 = substr(c_diag, 2, 4))   # ny kolonne: ICD-kode uden D-præfikset, 3 tegn
mutate(birth_year = year(foed_dag))   # fødselsår ud fra fødselsdato

transmute(ny_kolonne = udtryk)

Hvad det gør: som mutate(), men beholder kun de kolonner, du navngiver - resten smides væk. Praktisk, når du vil bygge en ren tabel med præcis de kolonner, en funktion skal bruge.

df %>% mutate(event = 1L)      # beholder ALLE kolonner + den nye 'event'
df %>% transmute(pnr, event = 1L)   # beholder KUN 'pnr' og 'event'

case_when(betingelse1 ~ værdi1, betingelse2 ~ værdi2, TRUE ~ standard)

Hvad det gør: en avanceret if-else med mange betingelser.

Anekdote: Tænk på trafiklyset: er det rødt → stop, er det gult → vær forsigtig, er det grønt → kør. case_when() fungerer på samme måde: betingelserne evalueres i rækkefølge, og den første betingelse, der er sand, bestemmer resultatet. TRUE ~ standard er “i alle andre tilfælde”-armen.

mutate(uddannelse_kat = case_when(
  edu_level == 1 ~ "Grundskole",
  edu_level == 2 ~ "Gymnasial",
  edu_level == 3 ~ "Videregaaende",
  TRUE           ~ "Ukendt"
))

if_else(betingelse, sand_værdi, falsk_værdi)

Hvad det gør: en simpel to-vejs betingelse.

if_else(is.na(death_date), 0L, 1L)   # 1 hvis personen er død, 0 hvis ikke

Strengere end basis-R’s ifelse() - begge værdier skal have samme type.

Note

0L og 1L er heltal (integers) i R. L-suffikset er R’s måde at angive at et tal er et heltal frem for et decimaltal (0 og 1 uden L er double som standard). if_else() kræver at begge værdier har præcis samme type - brug enten 0L/1L (integer) eller 0/1 (double), men ikke en blanding.


coalesce(x, erstatning)

Hvad det gør: erstatter NA-værdier med en anden værdi.

Anekdote: Efter et left join vil mange personer have NA i flag-kolonner, fordi de ikke havde den tilstand. coalesce(mi_flag, 0L) siger: “hvis mi_flag er NA, sæt det til 0 i stedet”. Bruges systematisk efter hvert left join, der producerer flag-kolonner.

coalesce(mi_flag, 0L)   # NA (= ikke fundet) -> 0 (= fraværende)

across(kolonner, funktion)

Hvad det gør: anvender den samme funktion på mange kolonner på én gang inde i mutate().

mutate(across(all_of(nmi_variabler), ~ coalesce(.x, 0L)))   # erstat NA med 0 i alle nmi-flagkolonner

~ coalesce(.x, 0L) er en anonym funktion: ~ betyder “funktion af”, og .x er den aktuelle kolonne. Det svarer til function(x) coalesce(x, 0L) - men kortere. across() kalder denne funktion én gang per kolonne i nmi_variabler.


rowSums(matrix, na.rm = TRUE)

Hvad det gør: lægger værdierne i hver række sammen på tværs af kolonner.

Bruges to steder:

  1. NMI-score (Nordic Multimorbidity Index): summerer produktet af 0/1-flag-kolonner og deres individuelle vægte → én vægtet comorbiditetsscore per person. En patient med hjertekarsygdom og cancer scorer højere end en patient med to mildere tilstande.
  2. Multimorbiditetstælling: summerer alle 0/1-flag → simpel optælling af antal tilstande per person.

Rækker: filtrer og deduplikér

filter(betingelse1, betingelse2, ...)

Hvad det gør: beholder kun de rækker, der opfylder betingelserne.

Anekdote: Du har en patientliste og vil kun se kvinder over 50 år. filter(koen == 2, alder > 50) er din si - alt andet ryger ud.

Tegn til at sammenligne en kolonne med en værdi:

Tegn Betyder Eksempel
== lig med (to lighedstegn - ét = er tildeling) filter(year == 2015)
!= forskellig fra filter(koen != 2)
> · >= større end · større end eller lig med filter(alder >= 18)
< · <= mindre end · mindre end eller lig med filter(alder < 65)
%in% er med i listen (se nedenfor) filter(icd3 %in% c("F00","F03"))

Tegn til at kombinere flere betingelser:

Tegn Betyder Eksempel
, eller & OG - begge skal være sande filter(koen == 2, alder > 50) → kvinder og over 50
\| ELLER - mindst én sand filter(icd3 == "F00" \| icd3 == "F03") → F00 eller F03
! IKKE - vend betingelsen filter(!is.na(alder)) → behold rækker hvor alder ikke mangler

Faldgrube - bland aldrig OG og ELLER uden parenteser. & binder før |, så filter(alder > 50 | alder < 18 & koen == 2) læses som alder > 50 | (alder < 18 & koen == 2). Mener du “(over 50 eller under 18) og kvinde”, skal du sætte parenteser: filter((alder > 50 | alder < 18) & koen == 2).

filter(c_diagtype %in% c("A", "B"))          # kun aktions- og bidiagnoser
filter(date_contact >= surgery_date)          # kun post-operative kontakter
filter(icd3 %in% c("G30", "F00", "F03"))     # kun demenskoder

%in% - “er med i listen”

Hvad det gør: tjekker om hvert element på venstre side optræder i vektoren på højre side. Returnerer TRUE eller FALSE for hvert element.

%in% er valgfrit: det er blot ét af filter()’s sammenligningstegn (ved siden af ==, >, < osv.). Du bruger det kun, når du vil matche mod en liste af værdier i stedet for en enkelt værdi - fx en kodeliste. Skal du matche pnr mod kohorten, så brug semi_join (se ovenfor).

Anekdote: Forestil dig en gæsteliste til en fest. icd3 %in% c("G30", "F00", "F03") er som at stille sig ved indgangen og tjekke: “er denne diagnosekode på listen?”

icd3 %in% c("G30", "F00", "F03")   # TRUE for disse tre koder, FALSE for alt andet
atc  %in% !!mine_atc               # TRUE for alle ATC-koder på din lokale liste (!! - se nedenfor)

Du vil se %in% i næsten alle filter()-kald i projektet.

Note

Skal du filtrere på pnr mod hele kohorten, så brug semi_join(tibble(pnr = kohort_pnrs), by = "pnr") i stedet for filter(pnr %in% ...) - det skubbes mere effektivt ned i databasen og kræver ikke !!.


distinct(kol1, kol2)

Hvad det gør: fjerner duplikater - beholder kun unikke kombinationer.

Anekdote: En person kan have fået diagnosen F00 ti gange. Du behøver kun at vide, om de nogensinde har haft den. distinct(pnr, icd3) reducerer det til én række per person per kode. distinct(pnr) giver dig bare listen over unikke person-ID’er.


slice(n)

Hvad det gør: beholder kun den n’te række inden for hver gruppe (bruges efter group_by()).

group_by(pnr) %>%          # gruppér per person
  arrange(desc(aar)) %>%   # nyeste år først
  slice(1)                 # beholder den nyeste post per person

Grupper og aggregering

group_by(kol1, kol2)

Hvad det gør: opdeler data i grupper, så efterfølgende operationer sker separat inden for hver gruppe.

Anekdote: Forestil dig at du har en stak patientkurver og sorterer dem i bunker efter CPR-nummer. group_by(pnr) gør præcis det - men kun i hukommelsen. Alle efterfølgende trin (arrange, slice, summarise) sker nu én bunke ad gangen.

group_by(pnr) %>%             # gruppér per person
  arrange(date_contact) %>%   # ældste dato først
  slice(1) %>%                # tidligste kontakt per person
  ungroup()                   # fjern grupperingen igen bagefter

ungroup()

Hvad det gør: fjerner grupperingen.

Vigtigt: kald altid ungroup() efter du er færdig med group_by(). Glemmer du det, forbliver data grupperet, og senere operationer kan opføre sig uventet.


arrange(kolonne) / arrange(desc(kolonne))

Hvad det gør: sorterer rækkerne stigende (standard) eller faldende (desc()).

Bruges typisk med group_by() %>% slice(1) for at finde den første eller nyeste post per person.


summarise(ny_kol = funktion(kol), .groups = "drop")

Hvad det gør: reducerer hver gruppe til én opsummeringsrække.

group_by(pnr) %>%                                                  # gruppér per person
  summarise(foerste_e66 = min(date_contact, na.rm = TRUE))         # tidligste E66-dato per person

.groups = "drop" fjerner grupperingen automatisk bagefter.


ntile(x, n)

Hvad det gør: inddeler rækker i n lige store grupper (kvantiler).

Warning

Bruges ikke til indkomstkvintiler i registerbaserede studier der følger SEPLINE-retningslinjerne. SEPLINE anbefaler at sammenligne med populationsspecifikke grænseværdier (Q20/Q40/Q60/Q80) stratificeret på køn × 5-årsaldersgruppe × referenceår - ikke ntile() på din kohorte alene. Se SEPLINE.


Join - sammensæt tabeller

Det er en af de ting, der tager længst tid at forstå, men som er afgørende for alt registerarbejde. En join sætter to tabeller sammen baseret på en fælles nøgle - typisk pnr.

inner_join(y, by = "nøgle")

Hvad det gør: beholder kun rækker, der eksisterer i BEGGE tabeller.

Anekdote: Det er som en VIP-liste ved indgangen. Du skal stå på BEGGE lister for at komme ind. Bruges når en match er meningsfuld - f.eks. inner_join(bs_kohort) beholder kun hospitalkontakter for personer, der faktisk er i studiet.

inner_join(bs_kohort %>% select(pnr, surgery_date), by = "pnr")   # kun kontakter fra BS-kohortens medlemmer

semi_join(y, by = "nøgle")

Hvad det gør: beholder de rækker fra den venstre tabel, der har en match i y - men tilføjer ingen kolonner fra y. Det er altså et filter, hvor listen af tilladte værdier ligger i en anden tabel.

Anekdote: Du har hele LPR og en liste over din kohorte. semi_join beholder kun de LPR-rækker, hvis pnr står på kohorte-listen - samme resultat som filter(pnr %in% ...), men hurtigere og mere pålideligt på dovne (Arrow/DuckDB) tabeller.

lpr_adm %>%
  semi_join(tibble(pnr = kohort_pnrs), by = "pnr")   # behold kun kohortens rækker

Hvor ligger den lokale liste? Selve listen er den tibble, du sender som andet argument (y); by = "pnr" siger kun, hvilken kolonne der skal matches på - ikke hvor listen er. Fordi y er en almindelig tabel, behøver du ikke !!. Det er forskellen fra filter(pnr %in% !!kohort_pnrs), hvor vektoren står direkte i betingelsen og !! er det, der injicerer den. Brug semi_join til at filtrere et register ned til kohorten.

Hvad det gør: beholder alle rækker fra den venstre tabel. Rækker uden match i den højre tabel får NA for de kolonner, der kom fra højre.

Anekdote: Det er som at tjekke, om dine patienter har et bestemt fund, uden at smide nogen væk. Alle patienter er der stadig - dem med funnet har en dato, dem uden har NA. Bruges overalt, når du tilføjer flag og kovariater til kohorten.

full_cohort %>%                               # start med alle kohortmedlemmer
  left_join(dementia, by = "pnr")             # alle bevares; kun dem med demens får en dato

bind_rows(df1, df2, ...)

Hvad det gør: stakker data frames ovenpå hinanden (samme kolonner, flere rækker).

Anekdote: Som at tage tre bunker papir og lægge dem i én stak. Bruges fx til at kombinere LPR2 + psykiatrisk LPR2 + LPR3 til én samlet diagnosetabel.

bind_rows(lpr2_resultater, lpr2_psyk_resultater, lpr3_resultater)   # kombinér alle tre kildetabeller

Omformning

pivot_wider(names_from = kol, values_from = kol)

Hvad det gør: omdanner et langt format (én række per besøg) til et bredt format (én række per person med én kolonne per tidspunkt).

Anekdote: Forestil dig en patient med fem vejebesøg - alle i samme kolonne med fem rækker. pivot_wider() omdanner det til én enkelt række med fem kolonner: vaegt_3mo, vaegt_6mo, vaegt_12mo, osv. Bruges i udtræk af vægt- og insulinudkomme.


Doven forespørgsel

!! (bang-bang, to udråbstegn)

Hvad det gør: injicerer en lokal R-variabel ind i en DuckDB/dplyr-forespørgsel.

Anekdote: Forestil dig at du beder en assistent om at finde alle rækker med en kode fra en liste. Hvis du siger “find alle med en kode fra listen mine_atc”, vil assistenten kigge efter en kolonne i databasen med det navn - og den eksisterer ikke. Du skal sige: “find alle med en kode fra denne liste” og holde listen frem. !! er det, der svarer til at holde listen frem.

filter(atc %in% !!mine_atc)   # !! siger: "mine_atc er en R-vektor, ikke et kolonnenavn"

Du vil se !! foran lokale R-variabler (typisk kode- og år-lister) inde i filter()-kald.

Note

Til pnr-filtrering mod kohorten bruger du semi_join(tibble(pnr = kohort_pnrs), by = "pnr"), som tager den lokale tabel direkte og ikke kræver !!.


!!kolonnenavn := værdi (inde i mutate)

Hvad det gør: opretter en kolonne, hvis NAVN er bestemt af en R-variabel - ikke skrevet direkte i koden.

Normalt skriver du et fast kolonnenavn til venstre for = i mutate():

mutate(mi = 1L)   # opretter altid en kolonne der hedder "mi"

Men i NMI-beregningen (Nordic Multimorbidity Index) kører vi i en løkke over en liste af kroniske tilstande ("mi", "stroke", "diabetes", …) og vil oprette én kolonne per tilstand. Kolonnens navn er derfor gemt i en variabel:

tilstandsnavn <- "mi"   # variablen indeholder navnet som en tekststreng

mutate(!!tilstandsnavn := 1L)   # !! injicerer variablens indhold: svarer til mutate(mi = 1L)
# Næste iteration: tilstandsnavn <- "stroke" → mutate(stroke = 1L)

To ting er anderledes end normalt: - !!: som i filter(): injicerer R-variabelens indhold i stedet for at tolke den som et kolonnenavn - :=: bruges i stedet for =, fordi R kræver det, når venstre side af en tildeling er dynamisk. Det er ikke muligt at skrive mutate(!!navn = 1L) - kun mutate(!!navn := 1L) virker.

1L er et heltal (se as.integer() / 1L) - flag-kolonner gemmes som heltal for at spare hukommelse.


Tekstoperationer

substr(streng, start, slut)

Hvad det gør: udtrækker en del af en tekststreng baseret på tegnnumre.

Anekdote: Du har ICD-koden "DG30". DST har tilføjet et “D” foran - det hører ikke til i standard ICD-10. substr("DG30", 2, 4) siger: “giv mig tegnene fra position 2 til 4” og returnerer "G30".

substr(c_diag, 2, 4)   # 3-tegns kode: "DG30" -> "G30"
substr(c_diag, 2, 5)   # 4-tegns kode: "DI110" -> "I110"

paste0(x, y)

Hvad det gør: sætter tekststrenge sammen uden mellemrum.

paste0("C", 10:43)   # laver "C10", "C11", "C12", ..., "C43"

Bruges til kompakt opbygning af ICD-kodelister.


paste(x, y, sep = "_")

Hvad det gør: sætter tekststrenge sammen med et selvvalgt adskillelsestegn.

paste(koen, foedselsaar, sep = "_")   # laver f.eks. "1_1975" som matchingnøgle

toupper(x) / tolower(x)

Hvad det gør: konverterer tekst til hhv. store eller små bogstaver.

toupper(c_opr)   # sikrer at operationskoder matcher uanset bogstavsstørrelse

grepl(monster, x)

Hvad det gør: returnerer TRUE/FALSE for hvert element i x, der matcher et søgemønster (regulært udtryk).

Anekdote: Det er som “Ctrl+F” på et tekstdokument, men anvendt på hele kolonner på én gang. grepl("^C34", icd4) finder alle 4-tegns koder, der starter med C34.

grepl("^C34", icd4)   # TRUE for "C340", "C341", "C342", osv.

Bruges fx til at matche ICD-koder mod diagnosemønstre i comorbiditetsmål som NMI (Nordic Multimorbidity Index).


Tal og datoer

as.Date(x)

Hvad det gør: konverterer tekst eller datetime til et simpelt datoobjekt.

DST gemmer nogle dato-tidsangivelser som "2021-03-15 14:32:00". as.Date() fjerner tidsdelene og giver en ren kalenderdato.

as.Date(kont_starttidspunkt)   # "2021-03-15 14:32:00" -> 2021-03-15

ymd(x) / dmy(x) (fra lubridate)

Hvad det gør: læser datoer der ikke står på ISO-formen ("2021-03-15"). as.Date() antager ISO og giver NA på fx "15/03/2021" eller "15-03-2021"; de forgivende lubridate-parsere gætter rækkefølgen ud fra navnet (ymd = år-måned-dag, dmy = dag-måned-år, mdy = måned-dag-år).

ymd("2021-03-15")   # år-måned-dag  -> 2021-03-15
dmy("15/03/2021")   # dag-måned-år  -> 2021-03-15  (as.Date ville give NA)

year(dato) (fra lubridate)

Hvad det gør: trækker årstallet ud af en dato.

year(surgery_date)   # 2021-03-15 -> 2021

difftime(dato1, dato2, units = "days")

Hvad det gør: beregner forskellen mellem to datoer.

as.numeric(difftime(surgery_date, foed_dag, units = "days")) / 365.25   # alder ved operation i år

min(x, na.rm = TRUE) / max(x, na.rm = TRUE)

Hvad det gør: finder det mindste/største element i en vektor og ignorerer NA.

summarise(foerste_dato = min(date_contact, na.rm = TRUE))   # tidligste kontaktdato per person

pmin(x, y) / pmax(x, y)

Hvad det gør: sammenligner to vektorer position for position og returnerer det mindste/største for hvert element.

Anekdote: Forestil dig to lister med datoer - dødsdato og studieslutningstidspunkt. pmin(death_date, study_end) vælger for hver person, hvad der kom først.

pmin(death_date, as.Date("2024-12-31"))   # censureringsdato: enten dødsdato eller studieslutningstidspunkt
TipDatoer på tværs af guiden

Funktionerne ovenfor er byggeklodserne. De konkrete dato-opgaver er vist dér, hvor de hører til i forløbet:


as.integer(x) / 1L

Hvad det gør: konverterer til heltal.

L-suffikset (f.eks. 1L, 0L) angiver at det er et heltal og ikke et decimaltal. Flag-kolonner gemmes som heltal (1L/0L) for at spare hukommelse.


is.na(x)

Hvad det gør: returnerer TRUE for NA-værdier (manglende værdier).

filter(!is.na(pnr))          # fjern rækker uden person-ID
filter(!is.na(date_dementia)) # fjern rækker uden demensdato

set.seed(n)

Hvad det gør: fastsætter startpunktet for tilfældig talgeneration.

Anekdote: Forestil dig at du blander et kortspil. Uden set.seed() vil du blande forskelligt hver gang. Med set.seed(42) blander du altid på den samme måde - og kan dermed reproducere dine resultater præcist. Kald det altid inden matching-løkker for at sikre reproducerbarhed.

set.seed(42)                   # fastsæt tilfældighedsfrø for reproducerbarhed
sample(pool_pnrs, size = 5)   # vælger altid de samme 5 tilfældige pnrs

sample(x, størrelse)

Hvad det gør: trækker tilfældige elementer fra en vektor.

Bruges i matchningslogik til at udvælge tilfældige kontrolpersoner fra en pool.


Lister og løkker

split(df, gruppevektor)

Hvad det gør: opdeler en data frame i en liste af sub-data-frames, én per unik gruppe.

Anekdote: Forestil dig at du sorterer patientkort i bunker efter år og køn. split(pool, paste(koen, foedselsaar, sep = "_")) giver dig én bunke per kombination, så matchning-koden kan arbejde hurtigt inden for én bunke ad gangen.


vector("list", n)

Hvad det gør: opretter en tom liste med plads til n elementer.

Forudallokering er hurtigere end at lade R udvide listen ét element ad gangen i en løkke.


seq_len(n)

Hvad det gør: genererer talrækken 1, 2, …, n.

Sikrere end 1:n i løkker, fordi den håndterer tilfældet n = 0 korrekt.


unlist(liste, use.names = FALSE)

Hvad det gør: fladtrykker en liste af vektorer til én lang vektor.


Diagnostik og fejlsøgning

class(x)

Hvad det gør: fortæller dig, hvilken type objekt x er.

class(mit_objekt)
# "tbl_df" "data.frame"        -> data er i R-hukommelsen
# "tbl_duckdb_connection"      -> doven DuckDB-forespørgsel, ikke hentet endnu
# "Table" "ArrowObject"        -> doven Arrow-forbindelse, ikke hentet endnu

Tjek altid class() som det første, hvis du får en mærkelig fejl.


nrow(df)

Hvad det gør: returnerer antallet af rækker.

Bruges til at udskrive kohorte-størrelser og kontrollere, at eksklusioner har virket.


cat("tekst\n")

Hvad det gør: udskriver tekst til konsollen uden anførselstegn. \n er linjeskift.

Bruges til fremskridtsbeskeder: cat("Udtrækker NMI-score...\n").


stop("besked")

Hvad det gør: stopper koden med en fejlbesked.

Bruges til at give en forståelig fejl, hvis en påkrævet fil mangler.


stopifnot(betingelse)

Hvad det gør: et sikkerhedstjek. Er betingelsen TRUE, sker der ingenting, og koden fortsætter. Er den FALSE, stopper koden med en fejl. Brug det til antagelser, der SKAL holde, fx “én række per person”:

stopifnot(n_distinct(df$pnr) == nrow(df))   # fejler hvis der er dubletter

Hvad gør du, hvis det stopper? Så holder antagelsen ikke - fx er der mere end én række per person. Find dubletterne og ret årsagen:

df %>% count(pnr) %>% filter(n > 1)   # se hvilke pnr der går igen
df <- df %>% distinct(pnr, .keep_all = TRUE)   # behold én række per person (hvis det er rigtigt)

Ofte stammer dubletter fra et join, der gangede rækker op, eller fra dobbelt person-år-rækker i kildedata.


gc()

Hvad det gør: frigiver ubrugt hukommelse tilbage til operativsystemet.

rm(stort_register)   # fjern objektet fra R
gc()                 # frigiv hukommelsen

Brug det efter du er færdig med store registre - I deler RAM med alle andre på DST-serveren.


Pakkeoversigt

Se Register-overblik for bekræftede kolonnenavne på alle registre disse pakker arbejder mod.

Pakke Hvad den leverer
fastreg convert(), read_register() - SAS → parquet, og læs derefter registre via navn (CRAN)
dplyr %>%, filter, select, mutate, join, group_by, arrange, osv.
tidyr pivot_wider() - omformning fra langt til bredt format
lubridate year(), as.Date(), datoberegninger
arrow read_parquet(), write_parquet() - parquet-filhåndtering
haven read_sas() - læsning af SAS-datafiler (kun 00_prepare_dbso.R)
TipLæs mere

Generel uddybning i The Epidemiologist R Handbook (på engelsk):

Back to top