Pediatric Complex Chronic Conditions

library(medicalcoder)
packageVersion("medicalcoder")
## [1] '0.6.0'

Introduction

The Pediatric Complex Chronic Condition (PCCC) coding schema version 2 was published in 2014 (Feudtner et al. 2014) and updated to version 3 in 2024 (Feinstein et al. 2024). Both versions identify 11 conditions, each with multiple subconditions.

Syntactically valid names for complex chronic conditions
condition condition_label
1 congeni_genetic Other Congenital or Genetic Defect
2 cvd Cardiovascular
3 gi Gastrointestinal
4 hemato_immu Hematologic or Immunologic
5 malignancy Malignancy
6 metabolic Metabolic
7 misc Miscellaneous, Not Elsewhere Classified
8 neonatal Premature & Neonatal
9 neuromusc Neurologic or Neuromuscular
10 renal Renal Urologic
11 respiratory Respiratory

The PCCC system provides a standardized approach to identifying children with complex chronic conditions using administrative data. This has several important implications:

Without a common framework such as the PCCC, studies of chronic pediatric conditions would be fragmented, limiting their impact on both research and practice.

PCCC Version 2.0 vs PCCC Version 3.0

Versions 2 and 3 differ mainly in how technology dependence is treated. Many ICD codes map to both a primary condition and either technology dependence or transplant.

In both versions, transplant-related codes indicate organ system failure. A patient with such a code is flagged as having both a transplant and the related condition.

Technology dependence, however, diverges between versions. In version 2, the presence of a technology dependence code classifies the patient as having both the associated condition and technology dependence. For example, ICD-10 Z46.81 is both a metabolic and technology dependence code. A patient with this code is classified as having a metabolic condition and technology dependence.

Version 3 refines this rule: technology dependence codes are assessed conditionally, recognizing that many do not reflect chronic conditions.

Example: ICD-10 Z46.81 (Encounter for fitting and adjustment of insulin pump) is a metabolic and technology dependence code. If a patient had this code in their medical records then they would be classified has having a metabolic condition and a tech dependence. Under version 2, this patient would be flagged as having a metabolic condition and technology dependence. Under version 3, the patient would only be flagged with a metabolic condition and technology dependence if at least one non-technology condition is flagged.

ICD Codes for PCCC

Let’s look at the codes that are in the PCCC schema. Calling get_pccc_codes returns a data.frame.

pccc_codes <- get_pccc_codes()
str(pccc_codes)
## 'data.frame':    7913 obs. of  12 variables:
##  $ icdv           : int  9 9 9 9 9 9 9 9 9 9 ...
##  $ dx             : int  0 0 0 0 0 0 0 0 0 0 ...
##  $ full_code      : chr  "00.10" "00.50" "00.51" "00.53" ...
##  $ code           : chr  "0010" "0050" "0051" "0053" ...
##  $ condition      : chr  "malignancy" "cvd" "cvd" "cvd" ...
##  $ subcondition   : chr  "neoplasms" "device_and_technology_use" "device_and_technology_use" "device_and_technology_use" ...
##  $ transplant_flag: int  0 0 0 0 0 0 0 1 1 1 ...
##  $ tech_dep_flag  : int  0 1 1 1 1 1 1 0 0 0 ...
##  $ pccc_v3.1      : int  1 1 1 1 1 1 1 1 1 1 ...
##  $ pccc_v3.0      : int  1 1 1 1 1 1 1 1 1 1 ...
##  $ pccc_v2.1      : int  1 1 1 1 1 1 1 1 1 1 ...
##  $ pccc_v2.0      : int  1 1 1 1 1 1 1 1 1 1 ...

The columns are:

Examples

Example: Consider a patient with following four diagnostic and two procedure ICD-9 codes:

pat1 <-
  data.frame(dx = c(1, 1, 1, 1, 0, 0),
             icdv = 9L,
             code = c("34590", "78065", "3432", "78065", "9929", "8606"))

An inner join between the pccc_codes and pat1 will yield the conditions this patient has.

merge(x = pccc_codes, y = pat1, all = FALSE, by = c("icdv", "dx", "code"))
##   icdv dx code full_code condition              subcondition transplant_flag
## 1    9  0 8606     86.06 metabolic device_and_technology_use               0
## 2    9  1 3432     343.2 neuromusc  infantile_cerebral_palsy               0
##   tech_dep_flag pccc_v3.1 pccc_v3.0 pccc_v2.1 pccc_v2.0
## 1             1         1         1         1         1
## 2             0         1         1         1         1

For all PCCC variants, there is one matching dx code, 343.2, for infantile cerebral palsy, matches for a neuromuscular condition. The procedure code 86.06 matches for a technology dependent metabolic condition.

Under version 2.0 of PCCC (variants pccc_v2.0 and pccc_v2.1), this patient has two conditions, neuromuscular, metabolic. This patient also has a flag for device and technology use.

pat1_pccc_v2.0 <-
  comorbidities(
       data = pat1,
       icd.codes = "code",
       dx.var = "dx",
       icdv = 9,
       method = "pccc_v2.0",
       flag.method = "current", # default
       poa = 1                  # default for flag.method = 'current'
  )

pat1_pccc_v2.1 <-
  comorbidities(
       data = pat1,
       icd.codes = "code",
       dx.var = "dx",
       icdv = 9,
       method = "pccc_v2.1",
       flag.method = "current",
       poa = 1
  )

all.equal(pat1_pccc_v2.0, pat1_pccc_v2.1, check.attributes = FALSE)
## [1] TRUE
pat1_pccc_v2.0
## 
## Comorbidities via pccc_v2.0
## 
##   congeni_genetic cvd gi hemato_immu malignancy metabolic misc neonatal
## 1               0   0  0           0          0         1    0        0
##   neuromusc renal respiratory any_tech_dep any_transplant num_cmrb cmrb_flag
## 1         1     0           0            1              0        2         1

Under version 3 of the PCCC, this patient has two conditions: neuromuscular and metabolic. The technology dependence flags are also 1 for this patient, but are not counted in the total number of conditions.

pat1_pccc_v3.0 <-
  comorbidities(data = pat1,
       icd.codes = "code",
       dx.var = "dx",
       icdv = 9,
       method = "pccc_v3.0",
       flag.method = 'current',
       poa = 1
  )

pat1_pccc_v3.1 <-
  comorbidities(data = pat1,
       icd.codes = "code",
       dx.var = "dx",
       icdv = 9,
       method = "pccc_v3.1",
       flag.method = 'current',
       poa = 1
  )

all.equal(pat1_pccc_v3.0, pat1_pccc_v3.1, check.attributes = FALSE)
## [1] TRUE

# retain the needed columns, there are four columns for each condition in v3
pat1_pccc_v3.0[, grep("^(cmrb_flag|num_cmrb|neuromus|metabolic|tech_dep_flag)", names(pat1_pccc_v3.0))]
##   metabolic_dxpr_only metabolic_tech_only metabolic_dxpr_and_tech
## 1                   0                   1                       0
##   metabolic_dxpr_or_tech neuromusc_dxpr_only neuromusc_tech_only
## 1                      1                   1                   0
##   neuromusc_dxpr_and_tech neuromusc_dxpr_or_tech num_cmrb cmrb_flag
## 1                       0                      1        2         1

In the output from version 3, we have four 0/1 indicator columns for each of the conditions.

The details in the list above might be easier to understand in a tabular form of possible sets. In the case of no conditions, only the <condition>_dxpr_or_tech columns are flagged as 0/1 with the <condition>_dxpr_only, <condition>_tech_only, and <condition>_dxpr_and_tech columns set to NA. When at least one condition is flagged, all the columns will be populated as 0/1.

cmrb_flag num_cmrb <condition>_dxpr_or_tech <condition>_dxpr_only <condition>_tech_only | <condition>_dxpr_and_tech | <other condition(s)>_dxpr_or_tech
0 0 0 0 0 0 0
1 1 0 0 0 0 1
1 1 1 1 0 0 0
1 1 1 1 1 1 0
1 >1 0 0 0 0 1
1 >1 1 0 1 0 1
1 >1 1 1 0 0 1
1 >1 1 1 1 1 1

Now, consider another patient, pat2, with the same codes as pat1 except for 3432, the code mapping to a neuromuscular condition.

pat2 <- subset(pat1, code != "3432")

Under version 2 of the PCCC this patient will still have metabolic and technology dependence conditions because of the code 86.06 is in the record, but will not have the neuromuscular condition.

pat2_pccc_v2.1 <-
  comorbidities(
    data = pat2,
    icd.codes = "code",
    dx.var = "dx",
    icdv = 9,
    method = "pccc_v2.1",
    flag.method = 'current',
    poa = 1
  )
Filter(f = function(x) x > 0, pat2_pccc_v2.1)
##   metabolic any_tech_dep num_cmrb cmrb_flag
## 1         1            1        1         1

Under version 3 of the PCCC, this patient will have no conditions. This is because no condition was identified based on non-technology dependent codes and thus the one technology dependent code is ignored.

pat2_pccc_v3.1 <-
  comorbidities(
    data = pat2,
    icd.codes = "code",
    dx.var = "dx",
    icdv = 9,
    method = "pccc_v3.1",
    flag.method = 'current',
    poa = 1
  )
Filter(f = function(x) x > 0, pat2_pccc_v3.1)
## data frame with 0 columns and 1 row

Expected Data Structures

The expected input data format for comorbidities is a “long” format. The only mandatory column is one column of ICD codes. These codes can be full codes (include the decimal point) or compact codes (omitting the decimal point). Additionally, column(s) for identifying patients, encounters, and any other important groups are encouraged. A column to indicate the ICD version (9 or 10), and another column for identifying the code as a diagnostic or procedure code are also encouraged. The example mdcr data set has three columns, a patient id (patid), the ICD compact codes (code), and a column to indicate if the ICD code is a diagnostic or procedure code, (dx: 1 for diagnostic, 0 for procedure).

The mdcr data is provided with columns for

head(mdcr)
##   patid icdv  code dx
## 1 71412    9 99931  1
## 2 71412    9 75169  1
## 3 71412    9 99591  1
## 4 71412    9 V5865  1
## 5 71412    9  V427  1
## 6 17087   10  V441  1
str(mdcr)
## 'data.frame':    319856 obs. of  4 variables:
##  $ patid: int  71412 71412 71412 71412 71412 17087 64424 64424 84361 84361 ...
##  $ icdv : int  9 9 9 9 9 10 9 9 9 9 ...
##  $ code : chr  "99931" "75169" "99591" "V5865" ...
##  $ dx   : int  1 1 1 1 1 1 1 0 1 1 ...

Applying pccc_v2.1 and pccc_v3.1 methods to mdcr could be as simple as:

mdcr_results_v2.1_01 <-
  comorbidities(data = mdcr,
       icd.codes = "code",
       id.vars = "patid",
       poa = 1,
       flag.method = 'current',
       method = "pccc_v2.1")

mdcr_results_v3.1_01 <-
  comorbidities(data = mdcr,
       icd.codes = "code",
       id.vars = "patid",
       poa = 1,
       flag.method = 'current',
       method = "pccc_v3.1")

and a useful summary of the object returned from comorbidities is gained by calling summary(). The return is a data.table with columns for the count and percentages. For pccc_v2.0 and pccc_v2.1 the condition, label, count, and percentage, are reported. For pccc_v3.0 and pccc_v3.1 the columns are extended to provide the counts and percentages for dxpr_or_tech, dxpr_only, tech_only, and dxpr_and_tech.

str(summary(mdcr_results_v2.1_01))
## 'data.frame':    24 obs. of  4 variables:
##  $ condition: chr  "congeni_genetic" "cvd" "gi" "hemato_immu" ...
##  $ label    : chr  "Other Congenital or Genetic Defect" "Cardiovascular" "Gastrointestinal" "Hematologic or Immunologic" ...
##  $ count    : int  3490 5034 6946 2887 4057 3348 1079 1572 5968 3033 ...
##  $ percent  : num  9.12 13.16 18.15 7.55 10.6 ...
str(summary(mdcr_results_v3.1_01))
## 'data.frame':    24 obs. of  10 variables:
##  $ condition            : chr  "congeni_genetic" "cvd" "gi" "hemato_immu" ...
##  $ label                : chr  "Other Congenital or Genetic Defect" "Cardiovascular" "Gastrointestinal" "Hematologic or Immunologic" ...
##  $ dxpr_or_tech_count   : int  3225 5185 5808 2943 4075 3466 774 1529 5917 2933 ...
##  $ dxpr_or_tech_percent : num  8.43 13.55 15.18 7.69 10.65 ...
##  $ dxpr_only_count      : int  3225 4526 1520 2943 4075 3415 126 1529 4605 1991 ...
##  $ dxpr_only_percent    : num  8.43 11.83 3.97 7.69 10.65 ...
##  $ tech_only_count      : int  0 318 3897 0 0 40 647 0 359 580 ...
##  $ tech_only_percent    : num  0 0.831 10.185 0 0 ...
##  $ dxpr_and_tech_count  : int  0 341 391 0 0 11 1 0 953 362 ...
##  $ dxpr_and_tech_percent: num  0 0.891 1.022 0 0 ...

The summary tables are data.frames and can be manipulated by the end user for reporting as they want, see the following table.

x <-
  merge(
    summary(mdcr_results_v2.1_01),
    summary(mdcr_results_v3.1_01),
    all = TRUE,
    by = c("condition", "label"),
    sort = FALSE
  )
x[["condition"]] <- NULL
Summary Table for mdcr_results_v2.1_01 and mdcr_results_v3.1_01.
v3.1
v2.1
dxpr or tech
dxpr only
tech only
dxpr and tech
count % count % count % count % count %
Conditions
Other Congenital or Genetic Defect 3490 9.1 3225 8.4 3225 8.4 0 0.0 0 0.0
Cardiovascular 5034 13.2 5185 13.6 4526 11.8 318 0.8 341 0.9
Gastrointestinal 6946 18.2 5808 15.2 1520 4.0 3897 10.2 391 1.0
Hematologic or Immunologic 2887 7.5 2943 7.7 2943 7.7 0 0.0 0 0.0
Malignancy 4057 10.6 4075 10.7 4075 10.7 0 0.0 0 0.0
Metabolic 3348 8.8 3466 9.1 3415 8.9 40 0.1 11 0.0
Miscellaneous, Not Elsewhere Classified 1079 2.8 774 2.0 126 0.3 647 1.7 1 0.0
Premature & Neonatal 1572 4.1 1529 4.0 1529 4.0 0 0.0 0 0.0
Neurologic or Neuromuscular 5968 15.6 5917 15.5 4605 12.0 359 0.9 953 2.5
Renal Urologic 3033 7.9 2933 7.7 1991 5.2 580 1.5 362 0.9
Respiratory 3327 8.7 3342 8.7 1858 4.9 823 2.2 661 1.7
Flags
Any Technology Dependence 9032 23.6 7207 18.8
Any Transplantation 1584 4.1 1653 4.3
Total Conditions
Any Condition 22815 59.6 21127 55.2
>= 2 conditions 11083 29.0 10987 28.7
>= 3 conditions 4709 12.3 4795 12.5
>= 4 conditions 1593 4.2 1691 4.4
>= 5 conditions 439 1.1 479 1.3
>= 6 conditions 90 0.2 101 0.3
>= 7 conditions 10 0.0 15 0.0
>= 8 conditions 2 0.0 2 0.0
>= 9 conditions 0 0.0 0 0.0
>= 10 conditions 0 0.0 0 0.0
>= 11 conditions 0 0.0 0 0.0

There are additional details we should consider with respect to the ICD codes. The ICD version, 9 or 10, and if the code is a diagnostic or a procedure code. For example, code ICD-9 diagnostic code 332.1 has the same compact code as ICD-9 procedure code 33.21, 3321. In the case of the mdcr data where we have only compact codes, the need to distinguish between ICD-9 diagnostic and ICD-9 procedure is critically important. In the mdcr data the code 3321 does appear as both diagnostic and procedure.

pccc_codes[pccc_codes$code == "3321", ]
##      icdv dx full_code code   condition              subcondition
## 59      9  0     33.21 3321 respiratory device_and_technology_use
## 1460    9  1     332.1 3321   neuromusc         movement_diseases
##      transplant_flag tech_dep_flag pccc_v3.1 pccc_v3.0 pccc_v2.1 pccc_v2.0
## 59                 0             1         1         1         1         1
## 1460               0             0         1         1         1         1
table(mdcr[mdcr$code == "3321", "dx"])
## 
##  0  1 
## 77  1

To account for the diagnostic or procedure status of the codes specify a value for the dx.var argument.

mdcr_results_v2.1_02 <-
  comorbidities(
    data = mdcr,
    id.vars = "patid",
    icd.codes = "code",
    dx.var = "dx",
    flag.method = 'current',
    poa = 1,
    method = "pccc_v2.1"
  )

mdcr_results_v3.1_02 <-
  comorbidities(
    data = mdcr,
    id.vars = "patid",
    icd.codes = "code",
    dx.var = "dx",
    flag.method = 'current',
    poa = 1,
    method = "pccc_v3.1"
  )

Specificity is increased by using the diagnostic/procedure flag. Using pccc_v2.1 there are 278 false positive flags when the diagnostic/procedure flag is omitted from the comorbidities call. Using pccc_v3.1 there are 256 false positive flags when the diagnostic/procedure flag is omitted from the comorbidities call.

# verify that the cmrb_flag and number of conditions is the same or less after
# accounting for the diagnostic/procedure flag in the comorbidities call
stopifnot(all(mdcr_results_v2.1_02$cmrb_flag <= mdcr_results_v2.1_01$cmrb_flag))
stopifnot(all(mdcr_results_v2.1_02$num_cmrb  <= mdcr_results_v2.1_01$num_cmrb))

sum(mdcr_results_v2.1_02$cmrb_flag != mdcr_results_v2.1_01$cmrb_flag)
## [1] 278
sum(mdcr_results_v2.1_02$num_cmrb  != mdcr_results_v2.1_01$num_cmrb)
## [1] 989

stopifnot(all(mdcr_results_v3.1_02$cmrb_flag <= mdcr_results_v3.1_01$cmrb_flag))
stopifnot(all(mdcr_results_v3.1_02$num_cmrb  <= mdcr_results_v3.1_01$num_cmrb))

sum(mdcr_results_v3.1_02$cmrb_flag != mdcr_results_v3.1_01$cmrb_flag)
## [1] 256
sum(mdcr_results_v3.1_02$num_cmrb  != mdcr_results_v3.1_01$num_cmrb)
## [1] 923

Let’s explore the record for patient 87420.

subset(mdcr, patid == "87420")
##      patid icdv  code dx
## 4073 87420    9 78321  1
## 4074 87420    9  5641  1
subset(get_pccc_codes(), code %in% c("78321", "5641"))
##     icdv dx full_code code condition subcondition transplant_flag tech_dep_flag
## 192    9  0     56.41 5641     renal        other               0             0
##     pccc_v3.1 pccc_v3.0 pccc_v2.1 pccc_v2.0
## 192         1         1         1         1
subset(mdcr_results_v2.1_01, patid == "87420", select = c("cmrb_flag", "renal"))
##       cmrb_flag renal
## 32849         1     1
subset(mdcr_results_v2.1_02, patid == "87420", select = c("cmrb_flag", "renal"))
##       cmrb_flag renal
## 32849         0     0

subset(mdcr_results_v3.1_01, patid == "87420", select = c("cmrb_flag", "renal_dxpr_or_tech"))
##       cmrb_flag renal_dxpr_or_tech
## 32849         1                  1
subset(mdcr_results_v3.1_02, patid == "87420", select = c("cmrb_flag", "renal_dxpr_or_tech"))
##       cmrb_flag renal_dxpr_or_tech
## 32849         0                  0

subset(get_icd_codes(with.descriptions = TRUE), full_code %in% c("56.41", "564.1"))
##       icdv dx full_code code src known_start known_end assignable_start
## 12530    9  1     564.1 5641 cms        1997      2015             1997
## 12531    9  1     564.1 5641 cms        1997      2015             1997
## 12532    9  0     56.41 5641 cms        1997      2015             1997
##       assignable_end                     desc desc_start desc_end
## 12530           2015          Irritable colon       1997     1999
## 12531           2015 Irritable bowel syndrome       2001     2015
## 12532           2015     Partial ureterectomy       1997     2015

In the above, the compact code “5641” matches procedure code 56.41 for a renal condition. In mdcr_results_v2.1_01 and mdcr_results_v3.1_01 where no distinction was made between diagnostic and procedure codes this patient was flagged as having a renal condition. However, when reviewing the patient record, the compact code “5641” is listed as a diagnostic criteria and the full code 564.1 is for Irritable bowel syndrome. This is an example of where discriminating between diagnostic and procedure codes is critically important when looking for complex chronic conditions.

If we explicitly look at an inner join between this patient’s data and the pccc lookup table we see that the code 5641 matches the procedure code in the pccc lookup table. By not accounting for diagnostic and procedure codes, the overlaps between the two coding structures can lead to false positives.

merge(x = subset(mdcr, patid == "87420"),
      y = pccc_codes,
      by.x = c("code"),
      by.y = c("code"),
      suffixes = c(".mdcr", ".pccc_codes")
)
##   code patid icdv.mdcr dx.mdcr icdv.pccc_codes dx.pccc_codes full_code
## 1 5641 87420         9       1               9             0     56.41
##   condition subcondition transplant_flag tech_dep_flag pccc_v3.1 pccc_v3.0
## 1     renal        other               0             0         1         1
##   pccc_v2.1 pccc_v2.0
## 1         1         1

Using full codes can prevent false positives too. Here are several different ways that comorbidities() could be called resulting in different outcomes.

Note: this is a good example of how medicalcoder can handle full and compact codes within a single record.

DF <- data.frame(id = c("full dx", "full pr", "compact dx", "compact pr"),
                 code = c("564.1", "56.41", "5641", "5641"),
                 dx = c(1, 0, 1, 0))

# ideal: using the dx/pr status and matching on full and compact codes.
comorbidities(
  data = DF,
  id.vars = "id",
  dx.var = "dx",
  icd.codes = "code",
  poa = 1,
  method = "pccc_v3.1"
)[, c("id", "cmrb_flag", "renal_dxpr_or_tech")]
##           id cmrb_flag renal_dxpr_or_tech
## 1 compact dx         0                  0
## 2 compact pr         1                  1
## 3    full dx         0                  0
## 4    full pr         1                  1

# false positive for the compact dx
comorbidities(
  data = DF,
  id.vars = "id",
  icd.codes = "code",
  poa = 1,
  method = "pccc_v3.1"
)[, c("id", "cmrb_flag", "renal_dxpr_or_tech")]
##           id cmrb_flag renal_dxpr_or_tech
## 1 compact dx         1                  1
## 2 compact pr         1                  1
## 3    full dx         0                  0
## 4    full pr         1                  1

# false negative for compact pr
comorbidities(
  data = DF,
  id.vars = "id",
  icd.codes = "code",
  poa = 1,
  full.code = TRUE,
  compact.codes = FALSE,
  method = "pccc_v3.1"
)[, c("id", "cmrb_flag", "renal_dxpr_or_tech")]
##           id cmrb_flag renal_dxpr_or_tech
## 1 compact dx         0                  0
## 2 compact pr         0                  0
## 3    full dx         0                  0
## 4    full pr         1                  1

# false positive for compact dx
comorbidities(
  data = DF,
  id.vars = "id",
  icd.codes = "code",
  poa = 1,
  full.code = FALSE,
  compact.codes = TRUE,
  method = "pccc_v3.1"
)[, c("id", "cmrb_flag", "renal_dxpr_or_tech")]
##           id cmrb_flag renal_dxpr_or_tech
## 1 compact dx         1                  1
## 2 compact pr         1                  1
## 3    full dx         0                  0
## 4    full pr         0                  0

# false negatives for compact and full pr
comorbidities(
  data = DF,
  id.vars = "id",
  icd.codes = "code",
  dx.var = "dx",
  poa = 1,
  full.code = FALSE,
  compact.codes = TRUE,
  method = "pccc_v3.1"
)[, c("id", "cmrb_flag", "renal_dxpr_or_tech")]
##           id cmrb_flag renal_dxpr_or_tech
## 1 compact dx         0                  0
## 2 compact pr         1                  1
## 3    full dx         0                  0
## 4    full pr         0                  0

Another consideration is the version of ICD, 9 or 10.

The record for patid 95471 is a great example of the problem that a compact code can cause. “E030” matches ICD-9 dx compact and full code E030 (no decimal point), and matches the ICD-10 dx compact code for full code E03.0 with only the ICD-10 version being related to a chronic complex condition.

Inputs to the comorbidities() call for the ICD version will impact the output. When calling comorbidities() with a variable to indicate the ICD version NA values will not be joined against and the codes are ignored resulting in no condition being flagged. If we know that we only want to compare against ICD-9 or ICD-10 values then using the icdv argument can simplify the call and in this case, no condition for ICD-9 and a condition is flagged for ICD-10.

subset(mdcr, patid == "95471")
##        patid icdv code dx
## 125330 95471   10 E030  1

# no flag becuse icdv = 9 which treats all input codes as ICD-9
comorbidities(
  data = subset(mdcr, patid == "95471"),
  icd.codes = "code",
  id.vars = 'patid',
  dx.var = "dx",
  icdv = 9L,
  poa = 1,
  method = "pccc_v3.1"
)[, c('patid', 'cmrb_flag')]
##   patid cmrb_flag
## 1 95471         0

# flag because icdv = 10 - same as using `icdv.var = "icdv"`
comorbidities(
  data = subset(mdcr, patid == "95471"),
  icd.codes = "code",
  id.vars = 'patid',
  dx.var = "dx",
  icdv = 10L,
  poa = 1,
  method = "pccc_v3.1"
)[, c('patid', 'cmrb_flag')]
##   patid cmrb_flag
## 1 95471         1

comorbidities(
  data = subset(mdcr, patid == "95471"),
  icd.codes = "code",
  id.vars = 'patid',
  dx.var = "dx",
  icdv.var = "icdv",
  poa = 1,
  method = "pccc_v3.0"
)[, c('patid', 'cmrb_flag')]
##   patid cmrb_flag
## 1 95471         1

Lastly, it should be noted that a lot of the ambiguity resulting from compact codes can be avoided when full codes are available. medicalcoder can handle both forms. In the example below we again use the “E030” and assess it against all full and compact codes (default), against only full codes, and lastly against only compact codes. Note in this example that we are not specifying the ICD version nor the diagnostic/procedure status of the code.

lookup_icd_codes("E030")
##   input_code   match_type icdv dx full_code code src known_start known_end
## 1       E030    full_code    9  1      E030 E030 cms        2010      2015
## 2       E030 compact_code   10  1     E03.0 E030 who        2008      2019
## 3       E030 compact_code   10  1     E03.0 E030 cdc        2001      2025
## 4       E030 compact_code   10  1     E03.0 E030 cms        2014      2026
##   assignable_start assignable_end
## 1             2010           2015
## 2             2008           2019
## 3             2001           2025
## 4             2014           2026
data <- data.frame(id = c("Ambiguous compact code", "Full ICD-9 code", "Full ICD-10 code"),
                   code  = c("E030", "E030", "E03.0"))
data
##                       id  code
## 1 Ambiguous compact code  E030
## 2        Full ICD-9 code  E030
## 3       Full ICD-10 code E03.0

args <-
  list(
    data = data,
    id.vars = "id",
    icd.codes = "code",
    poa = 1,
    method = "pccc_v3.1"
  )

default <-
  do.call(comorbidities, c(args, list(full.codes = TRUE,  compact.codes = TRUE )))
full_only <-
  do.call(comorbidities, c(args, list(full.codes = TRUE,  compact.codes = FALSE)))
compact_only <-
  do.call(comorbidities, c(args, list(full.codes = FALSE, compact.codes = TRUE )))

default[,       c("id", "cmrb_flag")]
##                       id cmrb_flag
## 1 Ambiguous compact code         1
## 2       Full ICD-10 code         1
## 3        Full ICD-9 code         1
full_only[,     c("id", "cmrb_flag")]
##                       id cmrb_flag
## 1 Ambiguous compact code         0
## 2       Full ICD-10 code         1
## 3        Full ICD-9 code         0
compact_only[,  c("id", "cmrb_flag")]
##                       id cmrb_flag
## 1 Ambiguous compact code         1
## 2       Full ICD-10 code         0
## 3        Full ICD-9 code         1

With no information about the “E030” being ICD-9 or ICD-10, full or compact, (can only be a diagnostic code in either ICD-9 or ICD-10) we get different flags. The default, the most liberal approach flags this example patient as having a condition in all cases. When only considering the code to be a full code, then only the ICD-10 version matches. When only considering the compact codes the flag is true for the ambiguous version and the ICD-9 full version since ICD-9 E030 is a full code with the same compact form.

Longitudinal Conditions

The medicalcoder package includes the example data set, mdcr_longitudinal, with ICD-9 and ICD-10 codes for 3 synthetic patients with multiple encounters. Each row has a date (encounter) for when the ICD code was reported.

head(mdcr_longitudinal)
##     patid       date icdv     code
## 1 9663901 2016-03-18   10   Z77.22
## 2 9663901 2016-03-24   10  IMO0002
## 3 9663901 2016-03-24   10 V87.7XXA
## 4 9663901 2016-03-25   10  J95.851
## 5 9663901 2016-03-30   10  IMO0002
## 6 9663901 2016-03-30   10    Z93.0

Let’s look at the pccc_v2.1 flags for each patient, using all the information from all the encounters. This can easily by done by specifying id.vars = c("patid") such that the comorbidities method considers call codes as occurring on one encounter.

longitudinal_v2_patid <-
  comorbidities(data = mdcr_longitudinal,
       icd.codes = "code",
       id.vars = c("patid"),
       icdv.var = "icdv",
       method = "pccc_v2.1",
       flag.method = "current",
       poa = 1
  )
kableExtra::kbl(longitudinal_v2_patid)
patid congeni_genetic cvd gi hemato_immu malignancy metabolic misc neonatal neuromusc renal respiratory any_tech_dep any_transplant num_cmrb cmrb_flag
231597 0 0 0 0 0 1 0 0 1 1 1 1 1 4 1
650838 1 0 1 0 0 0 0 1 1 1 0 1 0 5 1
9663901 0 0 1 0 0 0 0 0 1 0 1 1 0 3 1

We can look at the conditions flagged at each encounter by specifying the id.vars = c("patid", "date").

longitudinal_v2_patid_date <-
  comorbidities(data = mdcr_longitudinal,
       icd.codes = "code",
       id.vars = c("patid", "date"),
       icdv.var = "icdv",
       method = "pccc_v2.1",
       flag.method = "current",
       poa = 1)
kableExtra::kbl(
  subset(longitudinal_v2_patid_date, patid == "9663901"),
  row.names = FALSE
)
patid date congeni_genetic cvd gi hemato_immu malignancy metabolic misc neonatal neuromusc renal respiratory any_tech_dep any_transplant num_cmrb cmrb_flag
9663901 2016-03-18 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
9663901 2016-03-24 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
9663901 2016-03-25 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
9663901 2016-03-30 0 0 1 0 0 0 0 0 0 0 1 1 0 2 1
9663901 2016-05-19 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
9663901 2016-07-09 0 0 0 0 0 0 0 0 1 0 1 1 0 2 1
9663901 2017-01-31 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
9663901 2017-02-16 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
9663901 2018-03-29 0 0 0 0 0 0 0 0 0 0 1 1 0 1 1

Looking at patid 9663901 at an encounter level we see that the conditions occur at different moments in time and the condition the patient has change overtime. Because these are chronic conditions, once the condition is observed, it should be considered to exist in perpetuity.

For pccc_v2.1 a simple carry-forward method can be applied to the data set to mark the presence of a condition at the time of reporting and thereafter.

longitudinal_v2_patid_date_cumulative_poa0 <-
  comorbidities(
    data = mdcr_longitudinal,
    icd.codes = "code",
    id.vars = c("patid", "date"),
    icdv.var = "icdv",
    method = "pccc_v2.1",
    flag.method = "cumulative",
    poa = 0
  )

kableExtra::kbl(
  subset(longitudinal_v2_patid_date_cumulative_poa0, patid == "9663901"),
  row.names = FALSE
)
patid date congeni_genetic cvd gi hemato_immu malignancy metabolic misc neonatal neuromusc renal respiratory any_tech_dep any_transplant num_cmrb cmrb_flag
9663901 2016-03-18 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
9663901 2016-03-24 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
9663901 2016-03-25 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
9663901 2016-03-30 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
9663901 2016-05-19 0 0 1 0 0 0 0 0 0 0 1 1 0 2 1
9663901 2016-07-09 0 0 1 0 0 0 0 0 0 0 1 1 0 2 1
9663901 2017-01-31 0 0 1 0 0 0 0 0 1 0 1 1 0 3 1
9663901 2017-02-16 0 0 1 0 0 0 0 0 1 0 1 1 0 3 1
9663901 2018-03-29 0 0 1 0 0 0 0 0 1 0 1 1 0 3 1
longitudinal_v2_patid_date_cumulative_poa1 <-
  comorbidities(
    data = mdcr_longitudinal,
    icd.codes = "code",
    id.vars = c("patid", "date"),
    icdv.var = "icdv",
    method = "pccc_v2.1",
    flag.method = "cumulative",
    poa = 1
  )
kableExtra::kbl(
  subset(longitudinal_v2_patid_date_cumulative_poa1, patid == "9663901"),
  row.names = FALSE
)
patid date congeni_genetic cvd gi hemato_immu malignancy metabolic misc neonatal neuromusc renal respiratory any_tech_dep any_transplant num_cmrb cmrb_flag
9663901 2016-03-18 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
9663901 2016-03-24 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
9663901 2016-03-25 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
9663901 2016-03-30 0 0 1 0 0 0 0 0 0 0 1 1 0 2 1
9663901 2016-05-19 0 0 1 0 0 0 0 0 0 0 1 1 0 2 1
9663901 2016-07-09 0 0 1 0 0 0 0 0 1 0 1 1 0 3 1
9663901 2017-01-31 0 0 1 0 0 0 0 0 1 0 1 1 0 3 1
9663901 2017-02-16 0 0 1 0 0 0 0 0 1 0 1 1 0 3 1
9663901 2018-03-29 0 0 1 0 0 0 0 0 1 0 1 1 0 3 1

For pccc_v3.0 and pccc_v3.1 a simple carry-forward method would not be easy to use as information about technology dependent codes is omitted when non-technology dependent codes do not exist.

Let’s use three ICD-10 diagnostic codes for this example and we will explore all six possible permutations of the codes. We’ll generate a data set with seven encounters and one code appearing on each of encounters 2, 4, and 6.

The codes we’ll use are: * H49.811: metabolic (other metabolic disorders), * J84.111: respiratory (chronic respiratory diseases), and * Z96.41: metabolic (device and technology use).

codes <- c("H49.811", "J84.111", "Z96.41")
subset(get_pccc_codes(), full_code %in% codes)
##      icdv dx full_code   code   condition                 subcondition
## 6445   10  1   H49.811 H49811   metabolic    other_metabolic_disorders
## 6712   10  1   J84.111 J84111 respiratory chronic_respiratory_diseases
## 7905   10  1    Z96.41  Z9641   metabolic    device_and_technology_use
##      transplant_flag tech_dep_flag pccc_v3.1 pccc_v3.0 pccc_v2.1 pccc_v2.0
## 6445               0             0         1         1         1         1
## 6712               0             0         1         1         0         0
## 7905               0             1         1         1         1         1

The constructed data and permutations are:

permutations <-
  data.table::data.table(
    permutation = rep(1:6, each = 7),
    encounter_id = rep(1:7, times = 6),
    code =
      codes[c(NA, 1, NA, 2, NA, 3, NA,
              NA, 1, NA, 3, NA, 2, NA,
              NA, 2, NA, 1, NA, 3, NA,
              NA, 2, NA, 3, NA, 1, NA,
              NA, 3, NA, 1, NA, 2, NA,
              NA, 3, NA, 2, NA, 1, NA)]
  )

permutations[, plabel := paste(na.omit(code), collapse = ", "), by = .(permutation)]
permutations[, plabel := paste0("Permutation ", permutation, ": ", plabel)]
str(permutations, vec.len = 1)
## Classes 'data.table' and 'data.frame':   42 obs. of  4 variables:
##  $ permutation : int  1 1 ...
##  $ encounter_id: int  1 2 ...
##  $ code        : chr  NA ...
##  $ plabel      : chr  "Permutation 1: H49.811, J84.111, Z96.41" ...
##  - attr(*, ".internal.selfref")=<externalptr>

We’ll apply the pccc_v3.1 to this code set with flag.method = "cumulative" and all codes considered to be present-on-admission.

rtn <-
  comorbidities(
    data = permutations,
    icd.codes = "code",
    id.vars = c("permutation", "plabel", "encounter_id"),
    icdv = 10L,
    compact.codes = FALSE,
    method = "pccc_v3.1",
    flag.method = "cumulative",
    poa = 1
  )

Let’s walk through the results for each permutation.

Permutation 1

Permutation 1: H49.811, J84.111, Z96.41
Metabolic
Respiratory
encounter_id dxpr or tech dxpr only tech only dxpr and tech dxpr or tech dxpr only tech only dxpr and tech ccc flag num ccc
1 0 0 0 0 0 0 0 0 0 0
2 1 1 0 0 0 0 0 0 1 1
3 1 1 0 0 0 0 0 0 1 1
4 1 1 0 0 1 1 0 0 1 2
5 1 1 0 0 1 1 0 0 1 2
6 1 0 0 1 1 1 0 0 1 2
7 1 0 0 1 1 1 0 0 1 2

The first code to appear in this permutation is H49.811, metabolic (other). This is a diagnostic code and will flag the metabolic condition for encounters 2 through 7 as _dxpr_or_tech. The Z96.41 code, metabolic (tech), appears on encounter 6. Thus, for encounters 2 through 5 metabolic should be flagged as _dxpr_or_tech = 1, dxpr_only = 1, tech_only = 0, and dxpr_and_tech = 0. Encounters 6 and 7 then have dxpr_only = 0 and tech_only = 0 with dxpr_and_tech = 1. The J84.111 for respiratory is a non-tech code appearing on encounter 4 and should flag as dxpr_or_tech = 1, dxpr_only = 1, tech_only = 0, and dxpr_and_tech = 0 for encounters 4 through 7.

Permutation 2
Permutation 2: H49.811, Z96.41, J84.111
Metabolic
Respiratory
encounter_id dxpr or tech dxpr only tech only dxpr and tech dxpr or tech dxpr only tech only dxpr and tech ccc flag num ccc
1 0 0 0 0 0 0 0 0 0 0
2 1 1 0 0 0 0 0 0 1 1
3 1 1 0 0 0 0 0 0 1 1
4 1 0 0 1 0 0 0 0 1 1
5 1 0 0 1 0 0 0 0 1 1
6 1 0 0 1 1 1 0 0 1 2
7 1 0 0 1 1 1 0 0 1 2

As with permutation 1, having the non-tech dependent metabolic code H49.811 appearing on encounter 2 means that metabolic is flagged for encounters 2 through 7. What should differ is that dxpr_only is 1 for encounters 2 and 3, with dxpr_and_tech flagging for encounters 4 through 7. Lastly, the non-tech code J84.111 for respiratory condition flagging as dxpr_or_tech = dxpr_only = 1 for encounters 6 and 7.

Permutation 3
Permutation 3: J84.111, H49.811, Z96.41
Metabolic
Respiratory
encounter_id dxpr or tech dxpr only tech only dxpr and tech dxpr or tech dxpr only tech only dxpr and tech ccc flag num ccc
1 0 0 0 0 0 0 0 0 0 0
2 0 0 0 0 1 1 0 0 1 1
3 0 0 0 0 1 1 0 0 1 1
4 1 1 0 0 1 1 0 0 1 2
5 1 1 0 0 1 1 0 0 1 2
6 1 0 0 1 1 1 0 0 1 2
7 1 0 0 1 1 1 0 0 1 2

Permutation three has respiratory flagged for encounters 2 through 7. The non-tech metabolic code on encounter 4 results in the flagging of metabolic for encounters 4 through 7.

Permutation 4
Permutation 4: J84.111, Z96.41, H49.811
Metabolic
Respiratory
encounter_id dxpr or tech dxpr only tech only dxpr and tech dxpr or tech dxpr only tech only dxpr and tech ccc flag num ccc
1 0 0 0 0 0 0 0 0 0 0
2 0 0 0 0 1 1 0 0 1 1
3 0 0 0 0 1 1 0 0 1 1
4 1 0 1 0 1 1 0 0 1 2
5 1 0 1 0 1 1 0 0 1 2
6 1 0 0 1 1 1 0 0 1 2
7 1 0 0 1 1 1 0 0 1 2

Permutation 4 is notable as presence of the respiratory condition on encounters 2 through 7 means that when the technology dependent metabolic code appears on encounter 4, a metabolic is flagged for encounters 4 through 7. Compare this with permutations 5 and 6.

Permutation 5
Permutation 5: Z96.41, H49.811, J84.111
Metabolic
Respiratory
encounter_id dxpr or tech dxpr only tech only dxpr and tech dxpr or tech dxpr only tech only dxpr and tech ccc flag num ccc
1 0 0 0 0 0 0 0 0 0 0
2 0 0 0 0 0 0 0 0 0 0
3 0 0 0 0 0 0 0 0 0 0
4 1 0 0 1 0 0 0 0 1 1
5 1 0 0 1 0 0 0 0 1 1
6 1 0 0 1 1 1 0 0 1 2
7 1 0 0 1 1 1 0 0 1 2

For permutation 5 the first code is a tech dependent metabolic code on encounter 2. Because the only code for flagging a condition is a technology dependent code the PCCC version 3 algorithm results in no condition being flagged for encounters 2 and 3. On encounter 4, when the non-tech metabolic code appears then the metabolic condition is flagged and the past history of the technology dependent code persists.

Permutation 6
Permutation 6: Z96.41, J84.111, H49.811
Metabolic
Respiratory
encounter_id dxpr or tech dxpr only tech only dxpr and tech dxpr or tech dxpr only tech only dxpr and tech ccc flag num ccc
1 0 0 0 0 0 0 0 0 0 0
2 0 0 0 0 0 0 0 0 0 0
3 0 0 0 0 0 0 0 0 0 0
4 1 0 1 0 1 1 0 0 1 2
5 1 0 1 0 1 1 0 0 1 2
6 1 0 0 1 1 1 0 0 1 2
7 1 0 0 1 1 1 0 0 1 2

As with permutation 5, since the only code in the record for encounter 2 and 3 is the technology dependent metabolic code, there is no flagged condition. On encounter 4, when the dxpr code for a respiratory condition is reported then the respiratory condition and the metabolic condition is flagged as technology dependent. Note that technology only conditions are flagged if at least one other condition is flagged.

Subconditions

The documentation for PCCC version 2 (Feudtner et al. 2014) and version 3 (Feinstein et al. 2024) include subconditions under each of the major conditions. However, to our knowledge, no software prior to medicalcoder implemented flagging for the subconditions.

The subconditions for each condition are shown in the next table.

subcondition subcondition_label
congeni_genetic: Other Congenital or Genetic Defect
bone_and_joint_anomalies Bone And Joint Anomalies
chromosomal_anomalies Chromosomal Anomalies
diaphragm_and_abdominal_wall_anomalies Diaphragm And Abdominal Wall Anomalies
other_congenital_anomalies Other Congenital Anomalies
cvd: Cardiovascular
cardiomyopathies Cardiomyopathies
conduction_disorder Conduction Disorder
device_and_technology_use Device And Technology Use
dysrhythmias Dysrhythmias
endocardium_diseases Endocardium Diseases
heart_and_great_vessel_malformations Heart And Great Vessel Malformations
other Other
transplantation Transplantation
gi: Gastrointestinal
chronic_liver_disease_and_cirrhosis Chronic Liver Disease And Cirrhosis
congenital_anomalies Congenital Anomalies
device_and_technology_use Device And Technology Use
inflammatory_bowel_disease Inflammatory Bowel Disease
other Other
transplantation Transplantation
hemato_immu: Hematologic or Immunologic
acquired_immunodeficiency Acquired Immunodeficiency
aplastic_anemias Aplastic Anemias
coagulation_hemorrhagic Coagulation Hemorrhagic
diffuse_diseases_of_connective_tissue Diffuse Diseases Of Connective Tissue
hemophagocytic_syndromes Hemophagocytic Syndromes
hereditary_anemias Hereditary Anemias
hereditary_immunodeficiency Hereditary Immunodeficiency
leukopenia Leukopenia
other Other
polyarteritis_nodosa_and_related_conditions Polyarteritis Nodosa And Related Conditions
sarcoidosis Sarcoidosis
transplantation Transplantation
malignancy: Malignancy
neoplasms Neoplasms
transplantation Transplantation
metabolic: Metabolic
amino_acid_metabolism Amino Acid Metabolism
carbohydrate_metabolism Carbohydrate Metabolism
device_and_technology_use Device And Technology Use
endocrine_disorders Endocrine Disorders
lipid_metabolism Lipid Metabolism
other_metabolic_disorders Other Metabolic Disorders
storage_disorders Storage Disorders
misc: Miscellaneous, Not Elsewhere Classified
device_and_technology_use Device And Technology Use
transplantation Transplantation
neonatal: Premature & Neonatal
birth_asphyxia Birth Asphyxia
cerebral_hemorrhage_at_birth Cerebral Hemorrhage At Birth
extreme_immaturity Extreme Immaturity
fetal_malnutrition Fetal Malnutrition
hypoxic_ischemic_encephalopathy Hypoxic Ischemic Encephalopathy
other Other
respiratory_diseases Respiratory Diseases
spinal_cord_injury_at_birth Spinal Cord Injury At Birth
neuromusc: Neurologic or Neuromuscular
brain_and_spinal_cord_malformations Brain And Spinal Cord Malformations
cns_degeneration_and_diseases Cns Degeneration And Diseases
device_and_technology_use Device And Technology Use
epilepsy Epilepsy
infantile_cerebral_palsy Infantile Cerebral Palsy
intellectual_disabilities Intellectual Disabilities
movement_diseases Movement Diseases
muscular_dystrophies_and_myopathies Muscular Dystrophies And Myopathies
occlusion_of_cerebral_arteries Occlusion Of Cerebral Arteries
other_neurologic_disorders Other Neurologic Disorders
renal: Renal Urologic
chronic_bladder_diseases Chronic Bladder Diseases
chronic_renal_failure Chronic Renal Failure
congenital_anomalies Congenital Anomalies
device_and_technology_use Device And Technology Use
other Other
transplantation Transplantation
respiratory: Respiratory
chronic_respiratory_diseases Chronic Respiratory Diseases
cystic_fibrosis Cystic Fibrosis
device_and_technology_use Device And Technology Use
other Other
respiratory_malformations Respiratory Malformations
transplantation Transplantation

To get the subconditions all you need to do is use the subconditions = TRUE argument in the comorbidities call. For this example we will apply pccc_v3.1 with and without comorbidities.

without_subconditions <-
  comorbidities(
    data = mdcr,
    id.vars = "patid",
    icd.codes = "code",
    icdv.var = "icdv",
    dx.var = "dx",
    poa = 1,
    method = "pccc_v3.1",
    subconditions = FALSE
  )

with_subconditions <-
  comorbidities(
    data = mdcr,
    id.vars = "patid",
    icd.codes = "code",
    icdv.var = "icdv",
    dx.var = "dx",
    poa = 1,
    method = "pccc_v3.1",
    subconditions = TRUE
  )

The structure of the return object with_subconditions is a list with two elements. The first element, conditions, is identical to the results of calling comorbidities() with subconditions = FALSE.

with_subconditions
## 
## Comorbidities and Subconditions via pccc_v3.1
## 
## List of 2
##  $ conditions   :'data.frame':   38262 obs. of  49 variables:
##  $ subconditions:List of 11
##   ..$ congeni_genetic:'data.frame':  3225 obs. of  5 variables:
##   ..$ cvd            :'data.frame':  5122 obs. of  9 variables:
##   ..$ gi             :'data.frame':  5602 obs. of  7 variables:
##   ..$ hemato_immu    :'data.frame':  2832 obs. of  13 variables:
##   ..$ malignancy     :'data.frame':  3784 obs. of  3 variables:
##   ..$ metabolic      :'data.frame':  3109 obs. of  8 variables:
##   ..$ misc           :'data.frame':  762 obs. of  3 variables:
##   ..$ neonatal       :'data.frame':  1516 obs. of  9 variables:
##   ..$ neuromusc      :'data.frame':  5826 obs. of  11 variables:
##   ..$ renal          :'data.frame':  2768 obs. of  7 variables:
##   ..$ respiratory    :'data.frame':  3200 obs. of  7 variables:

all.equal(with_subconditions$conditions,
          without_subconditions,
          check.attributes = FALSE)
## [1] TRUE

The second element of with_subconditions is list of data.frames, one for each condition, with indicators for only those with the condition.

A quick and easy way to get a summary of the subconditions is to call summary().

str(
  summary(with_subconditions)
)
## 'data.frame':    82 obs. of  5 variables:
##  $ condition                      : chr  "congeni_genetic" "congeni_genetic" "congeni_genetic" "congeni_genetic" ...
##  $ subcondition                   : chr  NA "bone_and_joint_anomalies" "chromosomal_anomalies" "diaphragm_and_abdominal_wall_anomalies" ...
##  $ count                          : num  3225 825 1544 300 754 ...
##  $ percent_of_cohort              : num  8.429 2.156 4.035 0.784 1.971 ...
##  $ percent_of_those_with_condition: num  NA 25.6 47.9 9.3 23.4 ...

The subconditions are available for all pccc variants. A summary is presented in the following table.

v2.0
v2.1
v3.0
v3.1
count % of cohort % of those with condition count % of cohort % of those with condition count % of cohort % of those with condition count % of cohort % of those with condition
Other Congenital or Genetic Defect 3399 8.9 3490 9.1 3186 8.3 3225 8.4
Bone And Joint Anomalies 1239 3.2 36.5 1239 3.2 35.5 825 2.2 25.9 825 2.2 25.6
Chromosomal Anomalies 1509 3.9 44.4 1509 3.9 43.2 1544 4.0 48.5 1544 4.0 47.9
Diaphragm And Abdominal Wall Anomalies 300 0.8 8.8 300 0.8 8.6 300 0.8 9.4 300 0.8 9.3
Other Congenital Anomalies 552 1.4 16.2 671 1.8 19.2 709 1.9 22.3 754 2.0 23.4
Cardiovascular 4952 12.9 4952 12.9 5055 13.2 5122 13.4
Cardiomyopathies 240 0.6 4.8 240 0.6 4.8 239 0.6 4.7 240 0.6 4.7
Conduction Disorder 653 1.7 13.2 653 1.7 13.2 653 1.7 12.9 653 1.7 12.7
Device And Technology Use 438 1.1 8.8 438 1.1 8.8 586 1.5 11.6 587 1.5 11.5
Dysrhythmias 1130 3.0 22.8 1130 3.0 22.8 1130 3.0 22.4 1130 3.0 22.1
Endocardium Diseases 247 0.6 5.0 247 0.6 5.0 278 0.7 5.5 278 0.7 5.4
Heart And Great Vessel Malformations 2298 6.0 46.4 2298 6.0 46.4 2289 6.0 45.3 2320 6.1 45.3
Other 1071 2.8 21.6 1071 2.8 21.6 1177 3.1 23.3 1247 3.3 24.3
Transplantation 237 0.6 4.8 237 0.6 4.8 246 0.6 4.9 246 0.6 4.8
Gastrointestinal 6233 16.3 6264 16.4 5584 14.6 5602 14.6
Chronic Liver Disease And Cirrhosis 285 0.7 4.6 290 0.8 4.6 290 0.8 5.2 290 0.8 5.2
Congenital Anomalies 718 1.9 11.5 718 1.9 11.5 698 1.8 12.5 709 1.9 12.7
Device And Technology Use 4882 12.8 78.3 4882 12.8 77.9 4210 11.0 75.4 4217 11.0 75.3
Inflammatory Bowel Disease 264 0.7 4.2 264 0.7 4.2 264 0.7 4.7 264 0.7 4.7
Other 232 0.6 3.7 289 0.8 4.6 289 0.8 5.2 289 0.8 5.2
Transplantation 300 0.8 4.8 300 0.8 4.8 312 0.8 5.6 312 0.8 5.6
Hematologic or Immunologic 2695 7.0 2776 7.3 2766 7.2 2832 7.4
Acquired Immunodeficiency 11 0.0 0.4 11 0.0 0.4 11 0.0 0.4 11 0.0 0.4
Aplastic Anemias 823 2.2 30.5 823 2.2 29.6 776 2.0 28.1 823 2.2 29.1
Coagulation Hemorrhagic 98 0.3 3.6 100 0.3 3.6 100 0.3 3.6 100 0.3 3.5
Diffuse Diseases Of Connective Tissue 81 0.2 3.0 81 0.2 2.9 125 0.3 4.5 125 0.3 4.4
Hemophagocytic Syndromes 59 0.2 2.2 59 0.2 2.1 59 0.2 2.1 59 0.2 2.1
Hereditary Anemias 771 2.0 28.6 771 2.0 27.8 771 2.0 27.9 771 2.0 27.2
Hereditary Immunodeficiency 813 2.1 30.2 911 2.4 32.8 875 2.3 31.6 909 2.4 32.1
Leukopenia 28 0.1 1.0 28 0.1 1.0 28 0.1 1.0 28 0.1 1.0
Other 12 0.0 0.4 12 0.0 0.4 12 0.0 0.4 12 0.0 0.4
Polyarteritis Nodosa And Related Conditions 40 0.1 1.5 40 0.1 1.4 46 0.1 1.7 46 0.1 1.6
Sarcoidosis 2 0.0 0.1 2 0.0 0.1 3 0.0 0.1 3 0.0 0.1
Transplantation 142 0.4 5.3 142 0.4 5.1 181 0.5 6.5 181 0.5 6.4
Malignancy 3733 9.8 3766 9.8 3783 9.9 3784 9.9
Neoplasms 3525 9.2 94.4 3525 9.2 93.6 3524 9.2 93.2 3525 9.2 93.2
Transplantation 358 0.9 9.6 416 1.1 11.0 452 1.2 11.9 452 1.2 11.9
Metabolic 2983 7.8 3009 7.9 3100 8.1 3109 8.1
Amino Acid Metabolism 187 0.5 6.3 187 0.5 6.2 194 0.5 6.3 194 0.5 6.2
Carbohydrate Metabolism 130 0.3 4.4 130 0.3 4.3 130 0.3 4.2 130 0.3 4.2
Device And Technology Use 71 0.2 2.4 71 0.2 2.4 51 0.1 1.6 51 0.1 1.6
Endocrine Disorders 748 2.0 25.1 748 2.0 24.9 865 2.3 27.9 865 2.3 27.8
Lipid Metabolism 294 0.8 9.9 317 0.8 10.5 321 0.8 10.4 321 0.8 10.3
Other Metabolic Disorders 1736 4.5 58.2 1740 4.5 57.8 1620 4.2 52.3 1630 4.3 52.4
Storage Disorders 69 0.2 2.3 69 0.2 2.3 73 0.2 2.4 73 0.2 2.3
Miscellaneous, Not Elsewhere Classified 822 2.1 1010 2.6 759 2.0 762 2.0
Device And Technology Use 626 1.6 76.2 880 2.3 87.1 638 1.7 84.1 641 1.7 84.1
Transplantation 224 0.6 27.3 158 0.4 15.6 121 0.3 15.9 121 0.3 15.9
Premature & Neonatal 1559 4.1 1559 4.1 1513 4.0 1516 4.0
Birth Asphyxia 153 0.4 9.8 153 0.4 9.8 11 0.0 0.7 11 0.0 0.7
Cerebral Hemorrhage At Birth 35 0.1 2.2 35 0.1 2.2 84 0.2 5.6 84 0.2 5.5
Extreme Immaturity 349 0.9 22.4 349 0.9 22.4 349 0.9 23.1 349 0.9 23.0
Fetal Malnutrition 45 0.1 2.9 45 0.1 2.9 45 0.1 3.0 45 0.1 3.0
Hypoxic Ischemic Encephalopathy 130 0.3 8.3 130 0.3 8.3 127 0.3 8.4 130 0.3 8.6
Other 248 0.6 15.9 248 0.6 15.9 247 0.6 16.3 247 0.6 16.3
Respiratory Diseases 916 2.4 58.8 916 2.4 58.8 950 2.5 62.8 950 2.5 62.7
Spinal Cord Injury At Birth 0 0.0 0.0 0 0.0 0.0 0 0.0 0.0 0 0.0 0.0
Neurologic or Neuromuscular 5580 14.6 5732 15.0 5799 15.2 5826 15.2
Brain And Spinal Cord Malformations 1767 4.6 31.7 1767 4.6 30.8 1767 4.6 30.5 1767 4.6 30.3
Cns Degeneration And Diseases 1244 3.3 22.3 1247 3.3 21.8 1417 3.7 24.4 1474 3.9 25.3
Device And Technology Use 1384 3.6 24.8 1405 3.7 24.5 1240 3.2 21.4 1262 3.3 21.7
Epilepsy 759 2.0 13.6 759 2.0 13.2 833 2.2 14.4 833 2.2 14.3
Infantile Cerebral Palsy 1084 2.8 19.4 1322 3.5 23.1 1322 3.5 22.8 1322 3.5 22.7
Intellectual Disabilities 161 0.4 2.9 161 0.4 2.8 161 0.4 2.8 161 0.4 2.8
Movement Diseases 169 0.4 3.0 169 0.4 2.9 135 0.4 2.3 146 0.4 2.5
Muscular Dystrophies And Myopathies 147 0.4 2.6 147 0.4 2.6 147 0.4 2.5 147 0.4 2.5
Occlusion Of Cerebral Arteries 48 0.1 0.9 48 0.1 0.8 92 0.2 1.6 92 0.2 1.6
Other Neurologic Disorders 631 1.6 11.3 622 1.6 10.9 847 2.2 14.6 848 2.2 14.6
Renal Urologic 2807 7.3 2855 7.5 2728 7.1 2768 7.2
Chronic Bladder Diseases 507 1.3 18.1 507 1.3 17.8 519 1.4 19.0 519 1.4 18.8
Chronic Renal Failure 627 1.6 22.3 627 1.6 22.0 627 1.6 23.0 627 1.6 22.7
Congenital Anomalies 914 2.4 32.6 914 2.4 32.0 915 2.4 33.5 915 2.4 33.1
Device And Technology Use 950 2.5 33.8 1000 2.6 35.0 869 2.3 31.9 911 2.4 32.9
Other 216 0.6 7.7 217 0.6 7.6 217 0.6 8.0 217 0.6 7.8
Transplantation 288 0.8 10.3 288 0.8 10.1 303 0.8 11.1 303 0.8 10.9
Respiratory 3040 7.9 3040 7.9 3199 8.4 3200 8.4
Chronic Respiratory Diseases 330 0.9 10.9 330 0.9 10.9 1092 2.9 34.1 1092 2.9 34.1
Cystic Fibrosis 343 0.9 11.3 343 0.9 11.3 343 0.9 10.7 343 0.9 10.7
Device And Technology Use 1592 4.2 52.4 1592 4.2 52.4 1408 3.7 44.0 1409 3.7 44.0
Other 22 0.1 0.7 22 0.1 0.7 22 0.1 0.7 22 0.1 0.7
Respiratory Malformations 1094 2.9 36.0 1094 2.9 36.0 1091 2.9 34.1 1091 2.9 34.1
Transplantation 36 0.1 1.2 36 0.1 1.2 38 0.1 1.2 38 0.1 1.2

The longitudinal assessment for subconditions work as well. Using the same permutations data set from above we will look at the metabolic and respiratory conditions and subconditions.

rslts <-
  comorbidities(
    data = permutations,
    icd.codes = "code",
    id.vars = c("permutation", "plabel", "encounter_id"),
    icdv = 10L,
    compact.codes = FALSE,
    method = "pccc_v3.1",
    flag.method = "cumulative",
    poa = 1,
    subconditions = TRUE
  )

Let’s start by looking at the respiratory results. The only subcondition that should be, and is, flagged is chronic respiratory diseases. A reminder: the data.frame for a subcondition only report rows for when the primary condition was flagged. We see in the following encounters where the chronic respiratory disease is flagged is consistent with when the primary respiratory condition is flagged.

all(rslts$subconditions$respiratory$chronic_respiratory_diseases == 1)
## [1] TRUE
sapply(rslts$subconditions$respiratory[, -(1:3)], max)
## chronic_respiratory_diseases              cystic_fibrosis 
##                            1                            0 
##    device_and_technology_use                        other 
##                            0                            0 
##    respiratory_malformations              transplantation 
##                            0                            0

# which encounters flag for primary condition respiratory?
cnd <-
  rslts$conditions[
    respiratory_dxpr_or_tech == 1,
    .(cencid = paste(encounter_id, collapse = ", ")),
    by = .(plabel)
  ]


# which encounters flag for the subcondition chronic_respiratory_diseases?
scnd <-
  rslts$subconditions$respiratory[
    ,
    .(sencid = paste(encounter_id, collapse = ", ")),
    by = .(plabel)
  ]
Encounters flagging for respiratory condition and the chronic respiratory disease subcondition.
Encounters
Condition Subcondition
Permutation 1: H49.811, J84.111, Z96.41 4, 5, 6, 7 4, 5, 6, 7
Permutation 2: H49.811, Z96.41, J84.111 6, 7 6, 7
Permutation 3: J84.111, H49.811, Z96.41 2, 3, 4, 5, 6, 7 2, 3, 4, 5, 6, 7
Permutation 4: J84.111, Z96.41, H49.811 2, 3, 4, 5, 6, 7 2, 3, 4, 5, 6, 7
Permutation 5: Z96.41, H49.811, J84.111 6, 7 6, 7
Permutation 6: Z96.41, J84.111, H49.811 4, 5, 6, 7 4, 5, 6, 7

For the metabolic condition we have two subconditions to look at, 1) device and technology use, and 2) other metabolic disorders.

# which encounters flag for primary condition metabolic?
cnd <-
  rslts$conditions[
    metabolic_dxpr_or_tech == 1,
    .(cencid = paste(encounter_id, collapse = ", ")),
    by = .(plabel)
  ]

# which encounters flag for the subconditions?
scnd <-
  data.table::melt(
    rslts$subconditions$metabolic,
    id.vars = c("plabel", "encounter_id"),
    measure.vars = c("device_and_technology_use", "other_metabolic_disorders"),
    variable.factor = FALSE,
    variable.name = "subcondition"
  )
scnd <- scnd[value == 1]
scnd <-
  scnd[
    ,
    .(sencid = paste(encounter_id, collapse = ", ")),
    by = .(plabel, subcondition)
  ]

scnd <-
  data.table::dcast(
    scnd,
    plabel ~ subcondition,
    value.var = "sencid"
  )
Encounters flagging for a metabolic condition and the encounters flagging for subconidtions device and technology use and/or other metabolic disorders.
Encounters
Condition Device and Technology Use Other Metabolic Disorders
Permutation 1: H49.811, J84.111, Z96.41 2, 3, 4, 5, 6, 7 6, 7 2, 3, 4, 5, 6, 7
Permutation 2: H49.811, Z96.41, J84.111 2, 3, 4, 5, 6, 7 4, 5, 6, 7 2, 3, 4, 5, 6, 7
Permutation 3: J84.111, H49.811, Z96.41 4, 5, 6, 7 6, 7 4, 5, 6, 7
Permutation 4: J84.111, Z96.41, H49.811 4, 5, 6, 7 4, 5, 6, 7 6, 7
Permutation 5: Z96.41, H49.811, J84.111 4, 5, 6, 7 4, 5, 6, 7 4, 5, 6, 7
Permutation 6: Z96.41, J84.111, H49.811 4, 5, 6, 7 4, 5, 6, 7 6, 7

References

Feinstein, James A, Matt Hall, Amber Davidson, and Chris Feudtner. 2024. “Pediatric Complex Chronic Condition System Version 3.” JAMA Network Open 7 (7): e2420579–79. https://doi.org/10.1001/jamanetworkopen.2024.20579.
Feudtner, Chris, James A Feinstein, Wenjun Zhong, Matt Hall, and Dingwei Dai. 2014. “Pediatric Complex Chronic Conditions Classification System Version 2: Updated for ICD-10 and Complex Medical Technology Dependence and Transplantation.” BMC Pediatrics 14: 1–7. https://doi.org/10.1186/1471-2431-14-199.