The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.

The change-classification system

Design principles

Classification in trialdiff is deliberately boring and auditable:

There is no machine learning and no hidden scoring.

The built-in categories

library(trialdiff)
td_categories()
#> # A tibble: 16 × 2
#>    category                    label                         
#>    <chr>                       <chr>                         
#>  1 new_subject                 New subject                   
#>  2 subject_removed             Subject removed               
#>  3 new_visit                   New visit                     
#>  4 new_assessment              New assessment                
#>  5 new_record                  New record                    
#>  6 record_removed              Record removed                
#>  7 treatment_assignment_change Treatment-assignment change   
#>  8 derived_variable_change     Derived-variable change       
#>  9 corrected_value             Corrected value               
#> 10 missing_to_value            Missing to non-missing        
#> 11 value_to_missing            Non-missing to missing        
#> 12 variable_added              Variable added                
#> 13 variable_removed            Variable removed              
#> 14 type_change                 Type change                   
#> 15 label_change                Label change                  
#> 16 unclassified                Unclassified - requires review

The change register

diff <- compare_cut(adsl_cut1, adsl_cut2, by = "USUBJID", dataset = "ADSL")
reg <- as_register(diff)
reg[, c(".change_id", "record_type", "USUBJID", "variable", "old_value",
        "new_value", "change")]
#> # A tibble: 4 × 7
#>   .change_id record_type USUBJID     variable old_value new_value         change
#>   <chr>      <chr>       <chr>       <chr>    <chr>     <chr>             <chr> 
#> 1 CHG00001   added       01-718-1371 <NA>     <NA>      <NA>              recor…
#> 2 CHG00002   added       01-718-1427 <NA>     <NA>      <NA>              recor…
#> 3 CHG00003   modified    01-701-1015 TRT01P   Placebo   Xanomeline Low D… value 
#> 4 CHG00004   modified    01-701-1015 TRT01A   Placebo   Xanomeline Low D… value

Applying the default rules

classified <- classify_changes(diff)
classified$register[, c(".change_id", "category", "category_label", "reason")]
#> # A tibble: 4 × 4
#>   .change_id category                    category_label              reason     
#>   <chr>      <chr>                       <chr>                       <chr>      
#> 1 CHG00001   new_subject                 New subject                 Subject '0…
#> 2 CHG00002   new_subject                 New subject                 Subject '0…
#> 3 CHG00003   treatment_assignment_change Treatment-assignment change Treatment …
#> 4 CHG00004   treatment_assignment_change Treatment-assignment change Treatment …

Writing a custom rule

A rule is a predicate over the register. The context contains the dataset name, keys and the sets of subjects present in each cut.

age_rule <- td_rule(
  name = "age_change",
  label = "Age change",
  priority = 1L,
  test = function(register, context) {
    register$record_type == "modified" & register$variable == "AGE"
  },
  reason = function(register, context) {
    sprintf("Age changed for %s.", register$.subject)
  }
)

custom <- classify_changes(
  diff,
  rules = c(list(age_rule), td_default_rules())
)
custom$register$category[custom$register$variable == "AGE"]
#> [1] NA NA

Prioritisation

Rules are evaluated in ascending priority order. This matters when more than one rule could apply. For example, AVAL is a derived variable and a value can transition from missing to non-missing. Missingness transitions have a higher priority than the generic derived-variable rule, because they are more actionable for review:

old <- data.frame(USUBJID = c("S1", "S2"), AVAL = c(NA, 5))
new <- data.frame(USUBJID = c("S1", "S2"), AVAL = c(3, NA))
classified_missing <- classify_changes(compare_cut(old, new, by = "USUBJID",
                                                    dataset = "ADLB"))
classified_missing$modified[, c("USUBJID", "variable", "change", "category")]
#> # A tibble: 2 × 4
#>   USUBJID variable change           category        
#>   <chr>   <chr>    <chr>            <chr>           
#> 1 S1      AVAL     missing_to_value missing_to_value
#> 2 S2      AVAL     value_to_missing value_to_missing

Any change that no rule matches is marked unclassified and surfaced in the report as an item requiring manual review, so the rule set can never silently hide a change.

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.