Build and review a candidate code list

This workflow uses six entirely fabricated dictionary rows. The identifiers and observation counts are teaching examples, not genuine clinical codes or data. Users obtain a real dictionary separately through their CPRD access arrangements. Search output is a set of candidates to review against a study definition.

Load and inspect

library(cprd.search)
file <- system.file("extdata", "synthetic-dictionary.tsv", package = "cprd.search")
dictionary <- load_cprd_dictionary(file)
#> Loaded 6 medical terms.
names(dictionary)
#>  [1] "MedCodeId"             "Observations"          "OriginalReadCode"     
#>  [4] "CleansedReadCode"      "Term"                  "SnomedCTConceptId"    
#>  [7] "SnomedCTDescriptionId" "Release"               "EmisCodeCategoryId"   
#> [10] "term_lower"

MedCodeId, Term and Observations are required. Codes are read as character, while counts are numeric. A different explicit path switches dictionaries; force = TRUE refreshes the same path after a file changes.

Search, exclude and select

cprd_search("family history", "lung cancer")
#> Found 1 matching terms.
#> # A tibble: 1 × 9
#>   MedCodeId Observations OriginalReadCode CleansedReadCode Term                 
#>   <chr>            <dbl> <chr>            <chr>            <chr>                
#> 1 00004               30 B0001            B0001            family history of lu…
#> # ℹ 4 more variables: SnomedCTConceptId <chr>, SnomedCTDescriptionId <chr>,
#> #   Release <chr>, EmisCodeCategoryId <chr>
cprd_search_or("diabetes", "asthma", min_obs = 20)
#> Found 3 matching terms.
#> # A tibble: 3 × 9
#>   MedCodeId Observations OriginalReadCode CleansedReadCode Term                
#>   <chr>            <dbl> <chr>            <chr>            <chr>               
#> 1 00001              100 C10..            C10..            diabetes            
#> 2 00002               50 C10.1            C10.1            gestational diabetes
#> 3 00005               20 H33..            H33..            asthma              
#> # ℹ 4 more variables: SnomedCTConceptId <chr>, SnomedCTDescriptionId <chr>,
#> #   Release <chr>, EmisCodeCategoryId <chr>
review <- cprd_search_exclude("diabetes", exclude = "gestational")
#> Found 1 matching terms.
review[, c("MedCodeId", "Term")]
#> # A tibble: 1 × 2
#>   MedCodeId Term    
#>   <chr>     <chr>   
#> 1 00001     diabetes

Keywords are case-insensitive literal substrings. The default does not interpret code punctuation as a regular expression. Use regex = TRUE deliberately when patterns are needed:

cprd_search("H33..", column = "CleansedReadCode")
#> Found 1 matching terms.
#> # A tibble: 1 × 9
#>   MedCodeId Observations OriginalReadCode CleansedReadCode Term  
#>   <chr>            <dbl> <chr>            <chr>            <chr> 
#> 1 00005               20 H33..            H33..            asthma
#> # ℹ 4 more variables: SnomedCTConceptId <chr>, SnomedCTDescriptionId <chr>,
#> #   Release <chr>, EmisCodeCategoryId <chr>
cprd_search("^asthma$", regex = TRUE)
#> Found 1 matching terms.
#> # A tibble: 1 × 9
#>   MedCodeId Observations OriginalReadCode CleansedReadCode Term  
#>   <chr>            <dbl> <chr>            <chr>            <chr> 
#> 1 00005               20 H33..            H33..            asthma
#> # ℹ 4 more variables: SnomedCTConceptId <chr>, SnomedCTDescriptionId <chr>,
#> #   Release <chr>, EmisCodeCategoryId <chr>

Approximate complete-term matches

cprd_search_fuzzy("diabeties", select = c("MedCodeId", "Term", "dist"))
#> Found 1 fuzzy matches.
#> # A tibble: 1 × 3
#>   MedCodeId Term      dist
#>   <chr>     <chr>    <dbl>
#> 1 00001     diabetes     1
cprd_search_fuzzy("xiabetes", max_dist = 1)
#> Found 1 fuzzy matches.
#> # A tibble: 1 × 10
#>   MedCodeId Observations OriginalReadCode CleansedReadCode Term    
#>   <chr>            <dbl> <chr>            <chr>            <chr>   
#> 1 00001              100 C10..            C10..            diabetes
#> # ℹ 5 more variables: SnomedCTConceptId <chr>, SnomedCTDescriptionId <chr>,
#> #   Release <chr>, EmisCodeCategoryId <chr>, dist <dbl>

The default optimal-string-alignment distance counts edits to the whole term, with a maximum of two. It does not search each word inside longer phrases. jw, cosine and jaccard use normalized distances and default to 0.1; their thresholds must be within 0–1. Review approximate matches before including them in a code list.

Combine and export

asthma <- cprd_search("asthma", select = c("MedCodeId", "Term"))
#> Found 1 matching terms.
diabetes <- cprd_search("diabetes", select = c("MedCodeId", "Term"))
#> Found 2 matching terms.
codes <- cprd_combine(asthma, diabetes, diabetes)
#> Combined: 3 unique terms.
codes
#> # A tibble: 3 × 2
#>   MedCodeId Term                
#>   <chr>     <chr>               
#> 1 00005     asthma              
#> 2 00001     diabetes            
#> 3 00002     gestational diabetes

Combining keeps the first occurrence of each medical code. It sorts by observation count when available and otherwise retains input order.

Exporting requires an explicit output path; there is no default filename or directory. An existing file at that path is overwritten. Here we use a temporary file and remove it after reading it back.

output <- tempfile(fileext = ".csv")
cprd_export(codes, output)
#> Exported 3 rows to: /tmp/RtmpBPGW8P/file1cbbe24f2ef.csv
restored <- utils::read.csv(output, colClasses = "character")
stopifnot(identical(restored$MedCodeId, codes$MedCodeId))
restored
#>   MedCodeId                 Term
#> 1     00005               asthma
#> 2     00001             diabetes
#> 3     00002 gestational diabetes
unlink(output)

Always tell the importing software that identifier columns contain text. Automatic numeric conversion can remove leading zeroes or round long IDs. The package’s synthetic tests exercise export/reimport, search behavior, empty results and invalid input without requiring access to real CPRD files.