Skip to contents

Innleiing

Ved utrekning av kvalitetsindikatorar er det mange fallgruver å gå i. Me har derfor utvikla eit rammeverk for korleis dette skal gjerast. Ved hjelp av ein fastsett metodikk og eit tilhøyrande sett R-funksjonar reduserer me risikoen for at indikatorane vert rekna ut feil, og me gjer det raskare og enklare å bruka indikatorane i ulike situasjonar.

Det er greiast å visa rammeverket via nokre eksempel:

Eksempel – eittårsoppfølging

Me har eit enkelt register der pasientar vert opererte og seinare følgde opp. Nokre av variablane i registeret kan sjå slik ut:

#> # A tibble: 6 × 6
#>   pas_id kjonn  alder sjukehus dato_operasjon dato_oppfolging
#>    <int> <fct>  <dbl> <fct>    <date>         <date>         
#> 1     11 mann      18 A        2024-11-18     2025-11-01     
#> 2     13 kvinne    36 C        2026-01-22     NA             
#> 3     14 mann      25 B        2025-01-28     2026-07-04     
#> 4     27 kvinne    29 A        2025-05-14     2026-04-18     
#> 5     28 mann      78 B        2024-04-12     2026-03-28     
#> 6     29 mann      45 B        2025-01-03     NA

Pasientane skal helst følgjast opp innan eitt år etter operasjon, og me har ein eigen kvalitetsindikator for dette. Der er det blant anna definert at «eitt år» skal reknast som 365 dagar.

Første forsøk på utrekning

Viss me vart bedt om å rekna ut kvalitetsindikatoren, ville me kanskje gjort det slik:

sum(d_reg$dato_oppfolging - d_reg$dato_operasjon <= 365, na.rm = TRUE) / nrow(d_reg)
#> [1] 0.3333333

Så det ser ut til at berre ein tredel av pasientane får oppfølging innan fristen.

Det er dessverre fleire problem med utrekninga. For det første er svaret feil! Merk at pasient 13 har NA på oppfølgingsdatoen, som tyder at ho enno ikkje er følgd opp. Sidan det var mindre enn eitt år sidan ho vart operert (la oss anta at dagens dato er 25. September 2026), skal ho ekskluderast frå utrekninga. Det vert ho i utrekninga av teljaren (na.rm = TRUE), men ikkje i utrekninga av nemnaren (nrow(d_reg)), så brøken vert feil.

Dette er eksempel på ein feil som lett kan oppstå når utrekninga av teljaren og nemnaren vert gjort på ulike datakjelder. Spesielt er det stor risiko viss det er snakk om to ulike datarammer, så det bør ein aldri bruka.

Andre forsøk på utrekning

Problemet med to ulike datakjelder er lett å retta opp. Me kan for eksempel skriva det slik:

mean(d_reg$dato_oppfolging - d_reg$dato_operasjon <= 365, na.rm = TRUE)
#> [1] 0.5

Så halvparten av pasientane får oppfølging innan fristen. Mykje betre! Problemet er at svaret framleis er feil!

Pasient 29 vert ekskludert frå utrekninga, men me ser at han vart operert for godt over eit år sidan og har ikkje fått oppfølging enno (altså per 25. September 2026). Han bør altså reknast som ein som ikkje fekk oppfølging innan fristen. Og i motsetning til pasient 13, bør han ikkje ekskluderast frå utrekninga.

Eksempel på rammeverket

Rammeverket vårt løyser (delvis) problema ovanfor, blant anna ved at ein for brøkbaserte kvalitetsindikatorar (som for eksempel målar prosentdelen av pasientar som oppfyller eit kriterium) må vera meir eksplisitt på kven som skal inngå i teljaren og nemnaren.

Ein definerer på individnivå (radnivå) om ein observasjon (pasient, operasjon eller liknande) skal inngå i utrekninga av kvalitetsindikatoren, dvs. om han skal inngå i nemnaren i brøken. Tilsvarande definerer ein på individnivå om han skal inngå i teljaren.

Reint konkret lagar ein for kvar kvalitetsindikator ein R-funksjon (kvalitetsindikatorfunksjon, KI-funksjon) som tek inn ei dataramme på eit fastsett – og dokumentert – format, samt eventuelt andre argument. KI-funksjonen gjev ut ei dataramma med (iallfall) desse to variablane (lagde til):

  • ki_krit_nevner: Logisk variabel som seier om observasjonen oppfølgde kriteria for å vera med i utrekning av nemnaren i brøken.
  • ki_krit_teller: Logisk variabel som seier om observasjonen oppfølgde kriteria for å telja som eit «ja» i kvalitetsindikatoren, dvs. om han skal vera med i utrekning av teljaren i brøken.

Variabelen ki_krit_nevner må vera sann eller usann (dvs. ikkje NA) for alle radene. Variabelen ki_krit_teller må vera sann eller usann dersom ki_krit_nevner er sann, og usann eller NA dersom ki_krit_nevner er usann.

(Grunnen til at me ikkje tillèt NA-verdiar for ki_krit_teller dersom ki_krit_nevner er sann, er at for ein skikkeleg definert kvaltitetsindikator alltid skal kunna avgjera om ein observasjon som inngår i utrekninga av indikatoren oppfyller kriteria for å telja positivt i teljaren. Hadde me godteke NA, og for eksempel tolka dette som FALSE, hadde det vore stor risiko å gje ut feil svar, då ein NA-verdi lett kunne vera resultat av at KI-funksjonen ikkje var perfekt gjennomtenkt, og for eksempel feilaktig antok at alle variablane som inngjekk i utrekningane var ikkje-manglande. Ved å forby NA-verdiar, eliminerer me denne risikoen.)

fixme: Informasjon om attributt for namn og forklaring på KI-ar.

Eksempel på KI-funksjon

For eksempelet ovanfor kan KI-funksjonen sjå slik ut:

ki_oppf_innan_1_aar = function(d_reg, dato_datasett) {
  d_reg |>
    mutate(
      dagar_sidan_op = difftime(!!dato_datasett, dato_operasjon, units = "days"),
      dagar_til_oppf = difftime(dato_oppfolging, dato_operasjon, units = "days"),
      ki_krit_nevner = dagar_sidan_op >= 365,
      ki_krit_teller = ki_krit_nevner & !is.na(dato_oppfolging) & dagar_til_oppf <= 365
    )
}

Køyrer me funksjonen på datasettet, får me:

ki_oppf_innan_1_aar(d_reg, dato_datasett = Sys.Date()) |>
  print(width = Inf)
#> # A tibble: 6 × 10
#>   pas_id kjonn  alder sjukehus dato_operasjon dato_oppfolging dagar_sidan_op
#>    <int> <fct>  <dbl> <fct>    <date>         <date>          <drtn>        
#> 1     11 mann      18 A        2024-11-18     2025-11-01      676 days      
#> 2     13 kvinne    36 C        2026-01-22     NA              246 days      
#> 3     14 mann      25 B        2025-01-28     2026-07-04      605 days      
#> 4     27 kvinne    29 A        2025-05-14     2026-04-18      499 days      
#> 5     28 mann      78 B        2024-04-12     2026-03-28      896 days      
#> 6     29 mann      45 B        2025-01-03     NA              630 days      
#>   dagar_til_oppf ki_krit_nevner ki_krit_teller
#>   <drtn>         <lgl>          <lgl>         
#> 1 348 days       TRUE           TRUE          
#> 2  NA days       FALSE          FALSE         
#> 3 522 days       TRUE           FALSE         
#> 4 339 days       TRUE           TRUE          
#> 5 715 days       TRUE           FALSE         
#> 6  NA days       TRUE           FALSE

Ting å merka seg:

  • Om ein pasient inngår i kvalitetsindikatoren er avhengig av kva dato datasettet er frå. Her har me for enkelheits skuld brukt dagens dato (Sys.Date()), men i praksis må ein sjølvsagt bruka den reelle nedlastingsdatoen for datasettet.
  • Det er bevisst at me ikkje har sett Sys.Date() som standardverdi i funksjonen. Då kunne ein venja seg til å ikkje eksplisitt oppgje dato, fordi ein oftast har ferske data, som ville ført til feil resultat dei få gongane ein såg på historiske data.
  • I definisjonen av ki_krit_teller krev me først at ki_krit_nevner er sann. Dette er lurt, då det reduserer risikoen for at teljaren vert feil definert. I ein del tilfelle kan det òg gjera definisjonen av ki_krit_teller ein del enklare/kortare. (Det gjeld dei tilfella kriteria for inngå i nemnaren er veldig omfattande, med for eksempel bruk av mange variablar, mens kriteria for å inngå i teljaren er dei same pluss berre eitt eller nokre få tillegskriterium.)
  • Me brukar difftime() med eksplisitt definisjon av tidseining (units = "days"). Her ville R-koden dato_oppfolging - dato_operasjon hatt nøyaktig same effekt, men det er generelt ein mykje meir risikabel kode. Han ville ikkje fungera likt dersom datovariablane ein gong i framtida vart endra til tidsvariablar (POSIXct-objekt), dvs. om ein også registrerte klokkeslett for operasjon eller oppfølging. Men han ville oftast fungerte likt, så det hadde vore ein feil som hadde vore lett å oversjå. Det er nemleg slik at koden x - y ikkje alltid gjev ut talet på dagar mellom x og y når variablane er tidsvariablar; han gjev av og til ut dagar, av og til timar, av og til minutt og av og til sekund, avhengig av tidsavstanden. Derfor bør ein alltid bruka difftime() med eksplisitt val av units når ein reknar med tids- eller datovariablar.

Bruk av KI-funksjonen

Når ein har definert ein KI-funksjon, gjev rammeverket oss fleire verktøy for bruk av funksjonen. Oftast vil ein rett og slett rekna ut kvalitetsindikatoren. Det gjer ein enkelt med ein aggregeringsfunksjon. For binomiske/proporsjonsbaserte KI-funksjonar er dette aggreger_ki_prop():

d_reg |>
  ki_oppf_innan_1_aar(dato_datasett = Sys.Date()) |>
  aggreger_ki_prop()
#> # A tibble: 1 × 5
#>     est ki_teller ki_nevner konfint_nedre konfint_ovre
#>   <dbl>     <int>     <int>         <dbl>        <dbl>
#> 1   0.4         2         5         0.118        0.769

Her får me ut både estimatet av kvalitetsindikatoren (rett svar var altså 40 %), tala som inngår i teljaren og nemnaren og eit 95 %-konfidensintervall for indikatoren. Nokre ting å merka seg:

  • I praksis ville ein nok gjerne allerie ha lagra resultatet av ki_oppf_innan_1_aar() køyrd på datasettet som eit eige objekt, og så køyrd aggreger_ki_prop() på dette igjen. Men me viser her ei «røyr-køyring» for å illustrera at R-rammeverket er utforma slik at det skal kunna brukast i ein «tidyverse-dataflyt».
  • Sjølv om det var 6 rader, vart nemnaren berre 5, sidan det var éin pasient som var så nyleg opererert at han ikkje kunne inngå i utrekning av kvalitetsindikatoren.
  • Konfidensintervallet utrekna er Wilson-/skår-intervallet, som fungerer veldig godt for både verdiar av indikatoren nær 0 %, 100 % og andre verdiar, og for både små og store nemnarar, og er derfor det me anbefaler for generell bruk i kvalitetsindikatorsamanheng (sjå òg https://doi.org/10.1214/ss/1009213286). Konfidensintervalla ein får ut frå binom.test()-funksjonen (Clopper–Pearson-intervalla) vil vera breiare, og kan vera svært konservative i nokre tilfelle.
  • Det er sjølvsagt mogleg å velja konfidensnivå, dersom ein for eksempel vil ha 90 %- eller 99 %-konfidensintervall.
  • Dersom det hadde vore noko logisk feil med definisjonane av ki_krit_teller- og ki_krit_nevner-variablane, for eksempel om ki_krit_teller på ei rad var sann mens ki_krit_nevner var usann – noko som tyder på ein feil i programmeringa av KI-funksjonen – ville funksjonen ha gjeve ei feilmelding og nekta å rekna ut kvalitetsindikatoren.

Aggregering på gruppenivå

Ofte vil ein visa kvalitetsindikatorar stratifisert på sjukehus, diagnose eller tidsperiode, eller ein ein kombinasjon av dette. Det fiksar rammeverket for oss. La oss første laga eit objekt med inndata på individnivå.

d_oppf1 = d_reg |>
  ki_oppf_innan_1_aar(dato_datasett = Sys.Date())

Vil me visa resultata på sjukehusnivå, treng me berre gruppera d_oppf1 på vanleg vis før me køyrer aggregeringsfunksjonen:

d_oppf1 |>
  group_by(sjukehus) |>
  aggreger_ki_prop()
#> # A tibble: 3 × 6
#>   sjukehus   est ki_teller ki_nevner konfint_nedre konfint_ovre
#>   <fct>    <dbl>     <int>     <int>         <dbl>        <dbl>
#> 1 A            1         2         2         0.342        1    
#> 2 B            0         0         3         0            0.561
#> 3 C           NA         0         0        NA           NA

Ting å merka seg:

  • Funksjonen handterer fint tilfelle der nemnaren vert 0. Då vert både indikatoren og tilhøyrande konfidensgrenser NA (og ikkje for eksempel NaN, som 0/0 vert i R, eller ei feilmelding, som binom.test(0,0) gjev).
  • Rekkjefølgja sjukehusa vert vist på (her: ABC), svarar til nivåa til grupperingsvariabelen/-ane. Dette er ikkje nødvendigvis lik rekkjefølgja som radene opphavleg står i (her: ACB).
  • Om det manglar observasjonar for nokre av nivåa til grupperingsvariablane, følgjer utrekninga dei vanlege reglane for .drop-argumentet i group_by()-funksjonen. Men ein får ei åtvaring dersom .drop = FALSE og det manglar slike observasjonar. (fixme: Bør me ha dette? Viss ja, bør me ha det for alle grupper der nevner er 0?)

Her er eksempel på det siste punktet. Me fjernar pasientane for B-sjukehuset og reknar ut indikatoren på nytt, med to ulike verdiar for .drop:

d_oppf1_utan_b = filter(d_oppf1, sjukehus != "B")
d_oppf1_utan_b |>
  group_by(sjukehus, .drop = TRUE) |> # Standardverdi
  aggreger_ki_prop()
#> # A tibble: 2 × 6
#>   sjukehus   est ki_teller ki_nevner konfint_nedre konfint_ovre
#>   <fct>    <dbl>     <int>     <int>         <dbl>        <dbl>
#> 1 A            1         2         2         0.342            1
#> 2 C           NA         0         0        NA               NA
d_oppf1_utan_b |>
  group_by(sjukehus, .drop = FALSE) |>
  aggreger_ki_prop()
#> Warning in aggreger_ki_prop(group_by(d_oppf1_utan_b, sjukehus, .drop = FALSE)):
#> Det finnes grupper uten observasjoner i grupperingsvariabel
#> # A tibble: 3 × 6
#>   sjukehus   est ki_teller ki_nevner konfint_nedre konfint_ovre
#>   <fct>    <dbl>     <int>     <int>         <dbl>        <dbl>
#> 1 A            1         2         2         0.342            1
#> 2 B           NA         0         0        NA               NA
#> 3 C           NA         0         0        NA               NA

Fleksible KI-funksjonar

Det er òg mogleg å laga KI-funksjonar for ein familie av kvalitetsindikatorar. Her er eit eksempel.

Tidlegare såg me på oppfølging innan eitt år. I praksis bør eittårsoppfølginga skje etter cirka eitt år. Ein pasient som vart følgd opp seks veker etter operasjonen, bør derfor ikkje reknast til å ha fått eittårsoppfølging. Me innfører derfor omgrepet normtid. Ein person som har blitt følgd opp ved «1 år ± x dagar» (der x for eksempel er 30), vert rekna til å vera følgd opp innan normtid.

Me kan òg generalisera utrekninga til å gjelda oppfølging på vilkårlege andre tidspunkt (seksmånadsoppfølging, toårsoppfølging etc.):

ki_oppf_innan_normtid = function(d_reg, dato_datasett, oppf_aar, slingringsmonn_dagar = 30) {
  # Talet på dagar etter operasjon som skal reknast som
  # mål-tidspunkt (ideelt tidspunkt) for oppfølging,
  # pluss nedre og øvre grense for kva som er akseptabelt
  # (dvs. innanfor normtid)
  oppf_maal = oppf_aar * 365
  nedre_grense = oppf_maal - slingringsmonn_dagar
  ovre_grense = oppf_maal + slingringsmonn_dagar

  d_reg |>
    mutate(
      dagar_sidan_op = difftime(!!dato_datasett, dato_operasjon, units = "days"),
      dagar_til_oppf = difftime(dato_oppfolging, dato_operasjon, units = "days"),
      ki_krit_nevner = dagar_sidan_op >= ovre_grense,
      ki_krit_teller = ki_krit_nevner & !is.na(dato_oppfolging) &
        (dagar_til_oppf >= nedre_grense) & (dagar_til_oppf <= ovre_grense)
    )
}

I dette registeret brukar me vanlegvis eit slingringsmonn på 30 dagar for alle normtider, så det er sett som standardverdi (slingringsmonn_dagar = 30). Her er eksempel på bruk:

# Oppfølging innan 1 år ± 30 dagar
d_reg |>
  ki_oppf_innan_normtid(dato_datasett = Sys.Date(), oppf_aar = 1) |>
  aggreger_ki_prop()
#> # A tibble: 1 × 5
#>     est ki_teller ki_nevner konfint_nedre konfint_ovre
#>   <dbl>     <int>     <int>         <dbl>        <dbl>
#> 1   0.4         2         5         0.118        0.769

# Oppfølging innan 2 år ± 60 dagar, gruppert på sjukehus
d_reg |>
  ki_oppf_innan_normtid(
    dato_datasett = Sys.Date(),
    oppf_aar = 2, slingringsmonn_dagar = 60
  ) |>
  group_by(sjukehus) |>
  aggreger_ki_prop()
#> # A tibble: 3 × 6
#>   sjukehus   est ki_teller ki_nevner konfint_nedre konfint_ovre
#>   <fct>    <dbl>     <int>     <int>         <dbl>        <dbl>
#> 1 A           NA         0         0        NA               NA
#> 2 B            1         1         1         0.207            1
#> 3 C           NA         0         0        NA               NA

Viss me skal bruka utrekningane av eitt- og toårsoppfølging i fleire samanhengar, kan det vera greitt å definera eigne funksjonar for dette, med dei ulike argumenta sett til standardverdiar. Viss me alltid arbeider med ferske data (for eksempel direkte frå ein database), kan me for eksempel definera funksjonane slik:

ki_oppf_innan_1_aar = function(d_reg) {
  ki_oppf_innan_normtid(d_reg, dato_datasett = Sys.Date(), oppf_aar = 1)
}
ki_oppf_innan_2_aar = function(d_reg) {
  ki_oppf_innan_normtid(d_reg, dato_datasett = Sys.Date(), oppf_aar = 2)
}

Me brukar dei nydefinerte funksjonane som vanlege KI-funksjonar:

d_reg |>
  ki_oppf_innan_1_aar() |>
  aggreger_ki_prop()
#> # A tibble: 1 × 5
#>     est ki_teller ki_nevner konfint_nedre konfint_ovre
#>   <dbl>     <int>     <int>         <dbl>        <dbl>
#> 1   0.4         2         5         0.118        0.769

Viss det berre er eitt eller nokre få argument me vil låsa fast, kan me heller bruka argumentellipse (...) for dei resterande argumenta:

ki_oppf_innan_2_aar = function(...) {
  ki_oppf_innan_normtid(..., oppf_aar = 2)
}

Fleire inndatasett

I eksempla me har sett til no, har KI-funksjonane tatt eitt inndatasett (pluss nokre ekstraargument), og dette inndatasettet vart tilføyd nokre kolonnar. Men det er ingenting i vegen for at ein KI-funksjon kan ta fleire inndatasett. La oss sjå på eit eksempel.

For oppfølgingsindikatorane var det eigentleg urealistisk å ha berre éin variabel med oppfølgingsdato. Pasientane kan jo ha fleire oppfølgingar (ved seks månadar, eitt år, to år, …). Eit meir realistisk eksempel vil vera at me har to datasett, eitt for operasjonar og eitt for oppfølgingar, og ein koplingsnøkkel (pasient- og/eller operasjons-ID) mellom desse.

Viss me føreset at kvar pasient har berre éin operasjon men kan ha vilkårleg mange oppfølgingar, kan datasetta sjå slik ut:

d_oper # Operasjonar
#> # A tibble: 6 × 5
#>   pas_id kjonn  alder sjukehus dato_operasjon
#>    <int> <fct>  <dbl> <fct>    <date>        
#> 1     11 mann      18 A        2024-11-18    
#> 2     13 kvinne    36 C        2026-01-22    
#> 3     14 mann      25 B        2025-01-28    
#> 4     27 kvinne    29 A        2025-05-14    
#> 5     28 mann      78 B        2024-04-12    
#> 6     29 mann      45 B        2025-01-03
d_oppf # Oppfølgingar
#> # A tibble: 7 × 2
#>   pas_id dato_oppfolging
#>    <int> <date>         
#> 1     11 2025-11-01     
#> 2     11 2026-09-17     
#> 3     14 2026-07-04     
#> 4     27 2026-04-18     
#> 5     28 2025-04-02     
#> 6     28 2026-03-28     
#> 7     28 2026-08-05

Ein fleksibel funksjon for oppfølging innan normtid kan sjå slik ut:

ki_oppf_innan_normtid = function(d_oper, d_oppf, dato_datasett, oppf_aar, slingringsmonn_dagar = 30) {
  # Talet på dagar etter operasjon som skal reknast som
  # mål-tidspunkt (ideelt tidspunkt) for oppfølging,
  # pluss nedre og øvre grense for kva som er akseptabelt
  # (dvs. innanfor normtid)
  oppf_maal = oppf_aar * 365
  nedre_grense = oppf_maal - slingringsmonn_dagar
  ovre_grense = oppf_maal + slingringsmonn_dagar

  # For kvar operasjon, legg til info om eventuelle oppfølgingar
  d_reg = d_oper |>
    left_join(d_oppf, by = "pas_id", relationship = "one-to-many")

  # For kvar oppfølging, rekn ut kriterievariablar
  d_ki_per_oppf = d_reg |>
    mutate(
      dagar_sidan_op = difftime(!!dato_datasett, dato_operasjon, units = "days"),
      dagar_til_oppf = difftime(dato_oppfolging, dato_operasjon, units = "days"),
      ki_krit_nevner = dagar_sidan_op >= ovre_grense,
      ki_krit_teller = ki_krit_nevner & !is.na(dato_oppfolging) &
        (dagar_til_oppf >= nedre_grense) & (dagar_til_oppf <= ovre_grense)
    )

  # For kvar operasjon som *burde* vore følgt opp no, rekn ut kriterievariablar
  d_ki = d_ki_per_oppf |>
    filter(ki_krit_nevner) |>
    group_by(!!!syms(names(d_oper)), ki_krit_nevner) |>
    summarise(ki_krit_teller = any(ki_krit_teller)) |>
    ungroup()

  # Returner datasett med indikatorvariablar
  d_ki
}
  • For enkeltheits skuld gjev funksjonen her berre ut rader for pasientar der indikatoren kan reknast ut, altså der ki_krit_nevner er sann. Det gjer ting hakket enklare, men han kan òg lett utvidast til å gje ut rader for alle operasjonane.
  • Funksjonen kan sjølvsagt òg utvidast til å gje ut tilleggsvariablar som dagar_til_oppf. Men her må ein tenka godt gjennom implementasjon for å sikra at alt vert handtert riktig, for eksempel dersom det finst fleire oppfølgingar innan normtid på same operasjon.
  • Utrekningane vert i prinsippet gjort på same måte som før. Einaste forskjellen er at me no reknar først ut kriterievariablane for kvar oppfølging pasienten har hatt. Ein operasjon har oppfølging innan normtid dersom det finst minst éi slik oppfølging innnan normtid (any(ki_krit_teller)).
  • Me har brukt !!!syms(names(d_oper)) som grupperingsvariablar for å sikra at me får med alle kolonnane i operasjonsskjemaet, i tilfelle me seinare vil stratifisera på desse.
  • Det er òg viktig å avgruppera før me returnerer objektet, slik at seinare aggregering (som standard) vert gjort ugruppert.

Funksjonen brukar me på tilsvarande måte som før:

# Rekn ut kriteriedatasett
d_oppf1 = ki_oppf_innan_normtid(d_oper, d_oppf, dato_datasett = Sys.Date(), oppf_aar = 1)
#> `summarise()` has regrouped the output.
#> ℹ Summaries were computed grouped by pas_id, kjonn, alder, sjukehus,
#>   dato_operasjon, and ki_krit_nevner.
#> ℹ Output is grouped by pas_id, kjonn, alder, sjukehus, and dato_operasjon.
#> ℹ Use `summarise(.groups = "drop_last")` to silence this message.
#> ℹ Use `summarise(.by = c(pas_id, kjonn, alder, sjukehus, dato_operasjon,
#>   ki_krit_nevner))` for per-operation grouping (`?dplyr::dplyr_by`) instead.
d_oppf1
#> # A tibble: 5 × 7
#>   pas_id kjonn  alder sjukehus dato_operasjon ki_krit_nevner ki_krit_teller
#>    <int> <fct>  <dbl> <fct>    <date>         <lgl>          <lgl>         
#> 1     11 mann      18 A        2024-11-18     TRUE           TRUE          
#> 2     14 mann      25 B        2025-01-28     TRUE           FALSE         
#> 3     27 kvinne    29 A        2025-05-14     TRUE           TRUE          
#> 4     28 mann      78 B        2024-04-12     TRUE           TRUE          
#> 5     29 mann      45 B        2025-01-03     TRUE           FALSE

# Rekn ut sjølve kvalitetsindikatoren,
# både totalt og stratifisert på kjønn
d_oppf1 |>
  aggreger_ki_prop()
#> # A tibble: 1 × 5
#>     est ki_teller ki_nevner konfint_nedre konfint_ovre
#>   <dbl>     <int>     <int>         <dbl>        <dbl>
#> 1   0.6         3         5         0.231        0.882
d_oppf1 |>
  group_by(kjonn) |>
  aggreger_ki_prop()
#> # A tibble: 2 × 6
#>   kjonn    est ki_teller ki_nevner konfint_nedre konfint_ovre
#>   <fct>  <dbl>     <int>     <int>         <dbl>        <dbl>
#> 1 kvinne   1           1         1         0.207        1    
#> 2 mann     0.5         2         4         0.150        0.850

Ting å merka seg:

  • Funksjonen treng ikkje gje ut eit datasett med like mange rader som i nokon av inndatasetta. Her er det 6 operasjonar og 7 oppfølgingar, men KI-funksjonen gjev ut 5 rader. Det viktige er at han gjev ut data på individnivå (her: person-/operasjonsnivå).

Utrekning av totalrader

(fixme: Skal dokumentera korleis ein brukar funksjonen for totalrader.)

Kontinuerlege variablar

Til no har me sett på binomiske data. Men det kan lagast kvalitetsindikatorar for mange andre typar data. Her skal me sjå kontinuerlege data, meir spesifikt data på intervall- eller forholdstalsnivå. Dette er data der det gjev meining å snakka om gjennomsnittsverdiar, medianar og kvantilar.

Ein definerer KI-funksjonar som før, men no skal dei returnera datarammer med (iallfall) desse to variablane:

  • ki_x: Verdien til den kontinuerlege variabelen.
  • ki_aktuell: Logisk variabel som seier om observasjonen oppfølgde kriteria for å inngå i kvalitetsindikatoren (svarar altså til ki_krit_nevner for binomiske data).

Verdiane til ki_aktuell må vera sann eller usann, og ki_x må vera eit tal (ikkje NA) dersom ki_aktuell er sann.

Eksempel: langtidsblodsukker (HbA1c)

Eit kvalitetsregister har HbA1c-nivå på pasientane som ein kvalitetsindikator. Dette målar langtidsblodsukker (gjennomsnittleg blodsukkernivå over tid). Viss behandlinga er god, bør dette vera i intervallet 20–42 mmol/mol. Høge verdiar er negativt, og verdiar frå 48 mmol/mol indikerer diabetes. Her er eksempeldata:

d_reg_kont
#> # A tibble: 6 × 5
#>   pas_id kjonn  alder sjukehus hb1ac
#>    <int> <fct>  <dbl> <fct>    <dbl>
#> 1     11 mann      18 A         26.1
#> 2     13 kvinne    36 C         NA  
#> 3     14 mann      25 B         53.8
#> 4     27 kvinne    29 A         NA  
#> 5     28 mann      78 B         65.2
#> 6     29 mann      45 B         49.8

I det tilfellet er indikatoren ferdigutrekna, så KI-funksjonen vert svært enkelt:

ki_hb1ac = function(d_reg) {
  d_reg |>
    mutate(
      ki_x = hb1ac,
      ki_aktuell = !is.na(hb1ac)
    )
}

Aggergeringsfunksjonar for kontinurlege data

Det kan finnast fleire aggregeringsfunksjonar for kontinuerlege data. Førebels støttar me éin, for utrekning av gjennomsnittsverdiar, aggreger_ki_snitt():

# Rekn ut kvalitetsindikatoren
d_hba1c = d_reg_kont |>
  ki_hb1ac()

# Snittverdiar for heile registeret
d_hba1c |>
  aggreger_ki_snitt()
#> # A tibble: 1 × 4
#>     est konfint_nedre konfint_ovre n_aktuell
#>   <dbl>         <dbl>        <dbl>     <int>
#> 1  48.7          22.6         74.9         4

# Snittverdiar fordelt på sjukehus
d_hba1c |>
  group_by(sjukehus) |>
  aggreger_ki_snitt()
#> # A tibble: 3 × 5
#>   sjukehus   est konfint_nedre konfint_ovre n_aktuell
#>   <fct>    <dbl>         <dbl>        <dbl>     <int>
#> 1 A         26.1          NA           NA           1
#> 2 B         56.3          36.4         76.1         3
#> 3 C         NA            NA           NA           0

Så det ser ut til at pasientane frå sjukehus B gjer det dårlegare enn dei frå sjukehusa A (men me har sjølvsagt altfor lite data til å konkludera!).

Ting å merka seg:

  • Summen av n_aktuell er 4, sjølv om det var 6 pasientar i datasettet. Grunnen er at to av dei mangla HBA1C-verdi.
  • Konfidensintervallet vert rekna ut dersom det kan reknast ut. Viss det for eksempel er berre éin pasient i ei gruppe, vert det ikkje rekna ut. For sjukehus A får me gjennomsnitt men ikkje konfidensintervall, mens for sjukehus C får me ingen av delane.

Føresetnadane for å bruka funksjonen er dei same som for bruk av t-testar: Det må gje meining å rekna ut gjennomsnittsverdiar, og fordelinga til dei empiriske gjennomsnitta må kunna godt approksimerast av tilhøyrande t-fordeling (dette er vanlegvis oppfylt dersom ein har ikkje veldig få observasjonar per grupppe, eller viss verdiane innanfor kvar gruppe er normalfordelte).

(Me har òg langsiktige planar om å legga til støtte for bootstrap-baserte konfindensintervall for gjennomsnitta. Desse kan brukast der t-fordelinga ikkje er realistisk.)

Ratedata/Poisson-data

Ein annan type data er ratedata - data som følgjer ein Poissonfordeling.

KI-funksjonar defineres fortsatt på same måte, men denne gang skal dei returnera dataramme med (ialffall) desse tre variablane:

  • ki_antall: Antall hendingar som er blitt observert.
  • ki_eksponering: Perioden hendingane er blitt observert i.
  • ki_aktuell: Logisk variabel som seier om hendingane og eksponeringsperioden oppfølgde kriteria for å inngå i kvalitetsindikatoren (lignende som ki_aktuell for kontinuerlige data, og ki_krit_nevner for binomiske data).

Verdiane til ki_aktuell må vera sann eller usann, og ki_antall og ki_eksponering må vera eit heiltal (ikkje NA) dersom ki_aktuell er sann. Vidare må ki_eksponering vera større enn 0 (og ki_antall naturlegvis minst lik 0).

Eksempel: Infeksjonar per liggedøgn

Eit eksempel kan vera antall infeksjonar hos pasienter per liggedøgn.

d_reg_rate
#> # A tibble: 6 × 6
#>   pas_id kjonn  alder sjukehus infeksjonar liggedogn
#>    <int> <fct>  <dbl> <fct>          <dbl>     <dbl>
#> 1     11 mann      18 A                  0        10
#> 2     13 kvinne    36 C                 NA        NA
#> 3     14 mann      25 B                  2         8
#> 4     27 kvinne    29 A                 NA        11
#> 5     28 mann      78 B                  1         7
#> 6     29 mann      45 B                  2        11

I dette tilfellet er indikatoren ferdiutrekna, så KI-funksjonen vert svært enkelt:

ki_infeksjonar = function(d_reg) {
  d_reg |>
    mutate(
      ki_antall = infeksjonar,
      ki_eksponering = liggedogn,
      ki_aktuell = !(is.na(infeksjonar) | is.na(liggedogn))
    )
}

Aggergeringsfunksjonar for ratedata/Poisson-data

Aggregering av ratedata kan gjøres ved å anvende aggreger_ki_rate():

# Rekn ut kvalitetsindikatoren
d_infeksjonar = d_reg_rate |>
  ki_infeksjonar()

# Rateverdiar for heile registeret
d_infeksjonar |>
  aggreger_ki_rate()
#> # A tibble: 1 × 3
#>     est konfint_nedre konfint_ovre
#>   <dbl>         <dbl>        <dbl>
#> 1 0.139        0.0498        0.298

# Rateverdiar fordelt på sjukehus
d_infeksjonar |>
  group_by(sjukehus) |>
  aggreger_ki_rate()
#> # A tibble: 3 × 4
#>   sjukehus    est konfint_nedre konfint_ovre
#>   <fct>     <dbl>         <dbl>        <dbl>
#> 1 A         0            0             0.300
#> 2 B         0.192        0.0690        0.413
#> 3 C        NA           NA            NA

# Rateverdiar per uke
d_infeksjonar |>
  group_by(sjukehus) |>
  aggreger_ki_rate(multiplikator = 7)
#> # A tibble: 3 × 4
#>   sjukehus   est konfint_nedre konfint_ovre
#>   <fct>    <dbl>         <dbl>        <dbl>
#> 1 A         0            0             2.10
#> 2 B         1.35         0.483         2.89
#> 3 C        NA           NA            NA

Legg merke til at konfidensintervallet bereknast ved profil-likelihood-metoden. Dermed blir ikkje nødvendigvis konfidensintervallet symmetrisk om estimatet. Dette skal gi betre estimater når det er få hendingar som er blitt observert. Konfidensintervallet blir, som med dei andre aggregerings-funksjonane, kun berekna dersom det er mogleg. Ved ratedata er det ikkje mogleg kun dersom det ikkje er nokon datapunkt som er aktuelle, som ved sjukehus C i eksempelet.

Generelt og detaljar om rammeverket

(fixme: Etter alle eksempela ser me no på rammeverket generelt. Må forklara kva me meiner med nivå 1 og nivå 2-funksjonar (skal dei ha andre namn?), dokumentera kva eigenskapar funksjonar på ulike nivå må ha (inndata og utdata), og ha ei oversikt over dei ulike typane pakken støttar (p.t. berre funksjonar for brøkdata og for kontinuerlege funksjonar). Skriv om at me har planar om funksjonar for ratedata og (kanskje) levetidsdata. Kanskje òg ei liste med dei ulike aggregeringsfunksjonane. Litt om designprinsippa bak metodikken (eks. tidyverse-tankegang, robustheit, minimera risiko for feil, gjera jobben raskare og enklare, konsistente funksjonsnamn). Eit eksempel på korleis ein lagar sin eigen type. Kan bruka korrelasjon som eksempel (og brukaren kan utvida funksjonen til å støtta ulike korrelasjonstypar). Skriva om kor viktig det er at funksjonane er robuste (eksempel for t.test-funksjonen: med 0 observasjonar, med SD == 0, og med SD ~= 0). Detaljar/råd om namngjeving av funksjonar og objekt, og at funksjonar bør leggast i pakkar.)