Skip to contents

This small example runs entirely on your computer with invented records. No connection, credentials or clinical data are needed. Run one chunk at a time and change the code lists or dates. These codes illustrate matching, not validated clinical definitions or a study protocol.

Guide index | Guide 5 of 5

The detailed contracts are in time windows and condition flags. Here we put those operations together.

1. Make a tiny source

library(thipR)
library(dplyr)
#> 
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#> 
#>     filter, lag
#> The following objects are masked from 'package:base':
#> 
#>     intersect, setdiff, setequal, union

visits <- tibble::tibble(
  person = c("A", "A", "A", "B", "B", "C", "C"),
  adm = as.Date(c("2020-01-01", "2022-01-10", "2023-02-01",
                  "2019-06-01", "2020-01-01", "2020-01-01", "2020-06-01")),
  pdx = c("K35.8", "F12.1", "F20.0", "F20.0", "K35.8", "K35.8", "J06.9")
)
visits
#> # A tibble: 7 × 3
#>   person adm        pdx  
#>   <chr>  <date>     <chr>
#> 1 A      2020-01-01 K35.8
#> 2 A      2022-01-10 F12.1
#> 3 A      2023-02-01 F20.0
#> 4 B      2019-06-01 F20.0
#> 5 B      2020-01-01 K35.8
#> 6 C      2020-01-01 K35.8
#> 7 C      2020-06-01 J06.9

2. Choose candidate entries

For local data, use icd_flag() and ordinary filtering. Here one entry is the first recorded appendicitis admission per person, and time zero is the next day. A real study must specify admission/discharge timing and eligibility itself.

visits <- visits |> mutate(record_id = row_number())
entry_flags <- icd_flag(
  visits, conditions = list(appendicitis = "K35"),
  id = "record_id", cols = "pdx", match = "prefix"
)

entries <- visits |>
  left_join(entry_flags, by = "record_id") |>
  filter(appendicitis == TRUE) |>
  arrange(person, adm) |>
  distinct(person, .keep_all = TRUE) |>
  transmute(person, entry_id = record_id, index_date = adm + 1L)
entries
#> # A tibble: 3 × 3
#>   person entry_id index_date
#>   <chr>     <int> <date>    
#> 1 A             1 2020-01-02
#> 2 B             5 2020-01-02
#> 3 C             6 2020-01-02

icd_flag() returns flags rather than modifying its input. Dots, surrounding spaces and case are normalized. match = "exact" is the default; use "prefix" explicitly when subcodes should match.

3. Draw a history window

history <- thip_window(
  visits, entries, by = "person", date = "adm",
  index = "index_date", window = c(-365L, 0L)
) |>
  collect()
history
#>   person        adm   pdx record_id entry_id
#> 1      A 2020-01-01 K35.8         1        1
#> 2      B 2019-06-01 F20.0         4        5
#> 3      B 2020-01-01 K35.8         5        5
#> 4      C 2020-01-01 K35.8         6        6

This history window includes day −365 and excludes day 0.

4. Flag history and choose the cohort

history_flags <- icd_flag(
  history, conditions = list(prior_psychosis = paste0("F", 20:29)),
  id = "entry_id", cols = "pdx", match = "prefix"
)

screened <- entries |> left_join(history_flags, by = "entry_id")
cohort <- screened |> filter(prior_psychosis == FALSE)
cohort
#> # A tibble: 2 × 4
#>   person entry_id index_date prior_psychosis
#>   <chr>     <int> <date>     <lgl>          
#> 1 A             1 2020-01-02 FALSE          
#> 2 C             6 2020-01-02 FALSE

Person B is excluded by this example’s rule. An NA flag means all selected codes were blank/missing; an unmatched entry also becomes NA after the join. Neither is silently treated as negative. FALSE means recorded nonmatching codes, not proof of disease absence. More detailed quality rules belong in the study, rather than being inferred by icd_flag().

5. Follow retained people through a study end

Use explicit dates for follow-up that can span several years. Source records must actually cover the period; setting a window does not create observation.

followup_windows <- cohort |>
  mutate(followup_start = index_date,
         followup_end = as.Date("2024-01-01"))

followup <- thip_window(
  visits, followup_windows, by = "person", date = "adm",
  start = "followup_start", end = "followup_end"
) |>
  collect(batch_size = 100L)
followup
#>   person        adm   pdx record_id entry_id
#> 1      A 2022-01-10 F12.1         2        1
#> 2      A 2023-02-01 F20.0         3        1
#> 3      C 2020-06-01 J06.9         7        6

6. Flag conditions visibly

conditions <- list(cannabis = c("F12.1", "F12.2"), psychosis = "F20")
flags <- icd_flag(
  followup, conditions, id = "record_id", cols = "pdx", match = "prefix"
)
followup <- followup |> left_join(flags, by = "record_id")
followup
#>   person        adm   pdx record_id entry_id cannabis psychosis
#> 1      A 2022-01-10 F12.1         2        1     TRUE     FALSE
#> 2      A 2023-02-01 F20.0         3        1    FALSE      TRUE
#> 3      C 2020-06-01 J06.9         7        6    FALSE     FALSE

Here we flag by source record, then attach the flags to its entry window.

7. Summarize first dates with ordinary R code

cannabis_dates <- followup |>
  filter(cannabis == TRUE) |>
  group_by(entry_id) |>
  summarise(cannabis_date = min(adm), .groups = "drop")
psychosis_dates <- followup |>
  filter(psychosis == TRUE) |>
  group_by(entry_id) |>
  summarise(psychosis_date = min(adm), .groups = "drop")

results <- cohort |>
  left_join(cannabis_dates, by = "entry_id") |>
  left_join(psychosis_dates, by = "entry_id") |>
  mutate(days_cannabis_to_psychosis =
           as.integer(psychosis_date - cannabis_date))
results
#> # A tibble: 2 × 7
#>   person entry_id index_date prior_psychosis cannabis_date psychosis_date
#>   <chr>     <int> <date>     <lgl>           <date>        <date>        
#> 1 A             1 2020-01-02 FALSE           2022-01-10    2023-02-01    
#> 2 C             6 2020-01-02 FALSE           NA            NA            
#> # ℹ 1 more variable: days_cannabis_to_psychosis <int>

Person A enters in 2020, has cannabis recorded in 2022 and psychosis in 2023. Person C stays in the result without either event. The final table contains descriptive recorded-event dates, not a survival-analysis specification. Death censoring, exposure transitions and eligibility need explicit study rules; future exposure should not be assigned as a baseline characteristic.

Use a remote source

See prepare and filter sources for thip_table() and thip_filter_icd(). Candidate records enter R at collect(); thip_window() then retrieves each person’s records through another explicit collect(). The same ID must link across source years. Source coverage, eligibility and missing-data rules remain study decisions.