CTNote Library: Abstinence Outcomes

knitr::opts_chunk$set(echo = TRUE)
library(CTNote)
library(readxl)
library(kableExtra)
library(tidyverse)

Introduction

This document contains the algorithms necessary to code all the outcomes which measure "abstinence from substance use" or "relapse to substance use". We only include outcomes which result in a single value per subject. These outcomes are:

pathToTable1_char <- system.file(
  "suppl_docs", "definitions_20220405.xlsx", package = "CTNote", mustWork = TRUE
)
tab1_df <- readxl::read_xlsx(pathToTable1_char) 
tabTidy1_df <- 
  tab1_df %>% 
  select(-`Frequency of UOS`, -`Coded column name`, -DOI) %>% 
  rename(
    Group = `Outcome Group`,
    Endpoint = `Primary Endpoint`,
    Class = `Numeric Class`,
    Definition = `Definition/Assessment of Outcome`,
    `Missing is` = `Missing UOS coded as`
  ) %>% 
  mutate(
    Group = case_when(
      str_detect(Group, "Abstinence") ~ "Abstinence",
      str_detect(Group, "Relapse") ~ "Relapse",
      str_detect(Group, "Reduction") ~ "Reduction",
    )
  ) %>% 
  filter(Group == "Abstinence") %>% 
  arrange(Reference)

defns_char <- tabTidy1_df$Definition
names(defns_char) <- tabTidy1_df$Reference
tabTidy1_df  %>% 
  kable("html") %>%
  column_spec(1:4, width = "3cm") %>%
  column_spec(5, width = "5cm") %>%
  kable_styling("striped", font_size = 11) %>%
  kable_minimal() %>% 
  # All styling and spec calls have to come BEFORE this line.
  scroll_box(width = "1000px", height = "500px") 

We will use the table of participant opioid use patterns from the ctn0094DataExtra package to calculate these endpoints (we have a copy of the endpoints in the dataset outcomesCTN0094). Importantly, if you wish to apply these algorithms to calculate endpoints for your data, the participants' substance use patterns must be stored in the "substance use pattern word" format shown here. We also show a subset of the data to visualize a variety of different real substance use patterns.

We first define the following five-value legend:

###  Full Data  ###
udsOutcomes_df <- 
    CTNote::outcomesCTN0094 %>% 
  select(who, usePatternUDS)

# Make a copy
outcomesAbs_df <- udsOutcomes_df


###  Examples  ###
examplePeople_int <- c(1, 163, 210, 242, 4, 17, 13, 1103, 233, 2089)
outcomesAbs_df %>% 
  filter(who %in% examplePeople_int)

For example, participant 1 has a use pattern ooooooooooooooo (all missing UDS), which means that they dropped out of the study. In contrast, participant 233 has a use pattern *+++++++++++o++++++++++o (nearly all positive UDS): they did not drop out of the study, but the treatment was completely ineffective for them. Participant 2089 started the study in a rough patch, but greatly improved in treatment over time (++++---+--------------o-).



Abstinence from Substance Use Endpoints

@fiellin_counseling_2006

Definition: r defns_char["Fiellin et al., 2006"]; missing is positive

outcomesAbs_df <- 
    outcomesAbs_df %>%
  rowwise() %>% 
    mutate(
        udsPattern = recode_missing_visits(
            use_pattern = usePatternUDS,
        )
    ) %>% 
  # mixed results != abstinence
    mutate(
        udsPattern = recode_missing_visits(
            use_pattern = udsPattern,
            missing_is = "*"
        )
    ) %>% 
  # We did not code this definition with an "end", so participants with longer
  #   stays in treatment could have higher scores
    mutate(
        fiellin2006_abs = count_matches(
            use_pattern = udsPattern,
            match_is = "-"
        )
    ) %>% 
    select(who, fiellin2006_abs) %>% 
    left_join(outcomesAbs_df, ., by = "who")

outcomesAbs_df %>% 
  filter(who %in% examplePeople_int) %>% 
  select(who, usePatternUDS, fiellin2006_abs)

@kosten_buprenorphine_1993

Definition: r defns_char["Kosten et al., 1993"]

outcomesAbs_df <- 
    outcomesAbs_df %>%
  rowwise() %>% 
  mutate(
        kosten1993_isAbs = detect_subpattern(
            usePatternUDS,
            subpattern = "---" 
        )
    ) %>% 
    select(who, kosten1993_isAbs) %>% 
    left_join(outcomesAbs_df, ., by = "who")

outcomesAbs_df %>% 
  filter(who %in% examplePeople_int) %>% 
  select(who, usePatternUDS, kosten1993_isAbs)

@krupitsky_injectable_2011 (A) and (B)

whichKrupitsky_idx <- which(
  names(defns_char) == "Krupitsky et al., 2011"
)

There are two definitions from this paper which we include in the reduction section our library: r defns_char[[whichKrupitsky_idx[1]]] and r defns_char[[whichKrupitsky_idx[2]]].

Krupitsky et al., 2011 (A)

Definition: r defns_char[[whichKrupitsky_idx[1]]]

A comment on our algorithm: we do not know how long each protocol is exactly, so a pattern match approach (while intuitive at first) would not work. We will instead recode the use pattern as "negative" or "non-negative", and then check that the proportion of non-negative UDS is 0%.

outcomesAbs_df <- 
    outcomesAbs_df %>%
  rowwise() %>% 
    mutate(
        udsPattern = recode_missing_visits(
            use_pattern = usePatternUDS,
        )
    ) %>% 
    mutate(
        udsPattern = recode_missing_visits(
            use_pattern = udsPattern,
            missing_is = "*"
        )
    ) %>% 
    mutate(
        useProp = count_matches(
            use_pattern = udsPattern,
            match_is = "+",
            start = 5L,
            # Set this to the length of your protocol, or 24, whichever is shorter
            end = 15L,
            proportion = TRUE
        )
    ) %>% 
    mutate(krupitsky2011A_isAbs = useProp == 0) %>% 
    select(who, krupitsky2011A_isAbs) %>% 
    left_join(outcomesAbs_df, ., by = "who")

outcomesAbs_df %>% 
  filter(who %in% examplePeople_int) %>% 
  select(who, usePatternUDS, krupitsky2011A_isAbs)

Krupitsky et al., 2011 (B)

Definition: r defns_char[[whichKrupitsky_idx[2]]]

outcomesAbs_df <- 
    outcomesAbs_df %>%
  rowwise() %>% 
  mutate(
        udsPattern = recode_missing_visits(
            use_pattern = usePatternUDS,
        )
    ) %>% 
    mutate(
        udsPattern = recode_missing_visits(
            use_pattern = udsPattern,
            missing_is = "*"
        )
    ) %>% 
    mutate(
        krupitsky2011B_abs = count_matches(
            use_pattern = udsPattern,
            match_is = "-",
            start = 5L,
            # This trial protocol has a clear end date; we adjust it to our data
            end = 15L
        )
    ) %>% 
    select(who, krupitsky2011B_abs) %>% 
    left_join(outcomesAbs_df, ., by = "who")

outcomesAbs_df %>% 
  filter(who %in% examplePeople_int) %>% 
  select(who, usePatternUDS, krupitsky2011B_abs)

@ling_buprenorphine_1998

Definition: r defns_char["Ling et al., 1998"]; urine was screened 3 times per week

outcomesAbs_df <- 
    outcomesAbs_df %>%
  rowwise() %>% 
  mutate(
        ling1998_isAbs = detect_subpattern(
            use_pattern = usePatternUDS,
            # 13 consecutive UDS at 3x per week is 4.3 weeks
            subpattern = "----"
        )
    ) %>% 
    select(who, ling1998_isAbs) %>% 
    left_join(outcomesAbs_df, ., by = "who")


outcomesAbs_df %>% 
  filter(who %in% examplePeople_int) %>% 
  select(who, usePatternUDS, ling1998_isAbs)

@lofwall_weekly_2018

Definition: r defns_char["Lofwall et al., 2018"]

In their paper, abstinence was defined as 2 of 3 negative UDS for weeks 9, 10, and 11; negative UDS in week 12; and 5 or 6 UDS negative during weeks 13-24 (with alternating week visits, yielding 6 visits in this Phase II period). Because we have 15 weeks of data guaranteed, we scale this window and lattice. Their definition of abstinence is quite complex. Because we only have 15 weeks of data for most subjects, we shift their 12-week Phase I endpoint to week 7, and treat weeks 8-15 as Phase II. Also, we calculate these as proportions and not counts; this is so that these rules can be applied to windows of other sizes. The proportions would be the same---only the window of observation would change.

###  Define 15-week Lattice  ###
lofwallLattice_char <- collapse_lattice(
    lattice_patterns = c("o", "_o"),
    # For the lattice as defined over 24 weeks, you need 12 weeks of weekly visits
    #   and 6 sets of alternating "no visit" and "visit" week pairs, or c(12, 6).
    #   For us, we want 7 weeks straight of weekly visits followed by 4 pairs of
    #   alternating visits (8 weeks) for a total of 15 weeks.
    times = c(7, 4)
)
lofwallLattice_char


###  Calculate Weighted Abstinence  ###
outcomesAbs_df <- 
    outcomesAbs_df %>%
  rowwise() %>% 
  # Change mixed and missing results to positive
    mutate(
        udsPattern = recode_missing_visits(
            use_pattern = usePatternUDS,
            missing_is = "*"
        )
    ) %>% 
  mutate(
    udsPattern = recode_missing_visits(udsPattern)
  ) %>% 
  # "observe" only the UDS that would have been caught by the protocol
    mutate(
        udsLattice = view_by_lattice(
            use_pattern = udsPattern,
            lattice_pattern = str_sub(lofwallLattice_char, end = 15) # first 15 weeks
        )
    ) %>% 
    # Impute the visits that were not "observed"
    mutate(
        udsLatticeLOCF = impute_missing_visits(
            use_pattern = udsLattice,
            method = "locf",
            missing_is = "_",
            quietly = TRUE
        )
    ) %>% 
  # Count for Weeks 5-7; Week 8; and Weeks 9-15
    mutate(
        prop57 = count_matches(
            udsLatticeLOCF,
            match_is = "-",
            start = 5L,
            end = 7L,
            proportion = TRUE
        ),
        clean8 = count_matches(
            udsLatticeLOCF,
            match_is = "-",
            start = 8L,
            end = 8L
        ),
        prop915 = count_matches(
            udsLatticeLOCF,
            match_is = "-",
            start = 9L,
            end = 15L,
            proportion = TRUE
        ),
    ) %>% 
  # Check interval counts/proportions
    mutate(
        lofwall2018_isAbs = (prop57 >= 2/3) & (clean8 == 1) & (prop915 >= 5/6)
    ) %>% 
    select(who, lofwall2018_isAbs) %>% 
    left_join(outcomesAbs_df, ., by = "who")

outcomesAbs_df %>% 
  filter(who %in% examplePeople_int) %>% 
  select(who, usePatternUDS, lofwall2018_isAbs)

@mokri_medical_2016

Definition: r defns_char["Mokri, Chawarski, Taherinakhost, & Schottenfeld, 2016"]; missing is positive

outcomesAbs_df <- 
    outcomesAbs_df %>%
  rowwise() %>% 
  mutate(
        udsPattern = recode_missing_visits(
            use_pattern = usePatternUDS
        )
    ) %>%
    mutate(
        udsPattern = recode_missing_visits(
            use_pattern = udsPattern,
            missing_is = "*"
        )
    ) %>%
  # Find the number of weeks until the first "+"
    mutate(
        mokri2016_abs = detect_in_window(
            use_pattern = udsPattern,
            window_width = 1L,
            threshold = 1L
        )
    ) %>%
    unnest(cols = "mokri2016_abs", names_sep = "_") %>%
    select(who, starts_with("mokri2016_abs")) %>%
    left_join(outcomesAbs_df, ., by = "who")

outcomesAbs_df %>% 
  filter(who %in% examplePeople_int) %>% 
  select(who, usePatternUDS, starts_with("mokri2016_abs"))

If you are more comfortable using "survival" or "time-to-event" data structures, then the above definition can be modified by the following code:

outcomesAbs_df %>% 
  filter(who %in% examplePeople_int) %>% 
  mutate(
    mokri2016_wksAbst = survival::Surv(
      time = mokri2016_abs_time,
      event = mokri2016_abs_event
    )
  ) %>% 
  # FOR PRINTING THE TABLE ONLY. DO NOT USE NEXT LINE IN PRACTICE!!!
  mutate(mokri2016_wksAbst = as.character(mokri2016_wksAbst)) %>% 
  select(who, usePatternUDS, mokri2016_wksAbst)

@schottenfeld_methadone_2005

Definition: r defns_char["Schottenfeld et al., 2005"], missing is ignored

outcomesAbs_df <- 
    outcomesAbs_df %>%
  rowwise() %>% 
  # Ignore missing visits
  mutate(
        udsPattern = recode_missing_visits(
            use_pattern = usePatternUDS,
            missing_becomes = ""
        )
    ) %>% 
  # Mixed are positive
    mutate(
        udsPattern = recode_missing_visits(
            use_pattern = udsPattern,
            missing_is = "*"
        )
    ) %>% 
  # Measure the length of the longest period of continuous abstinence
    mutate(
        schottenfeld2005_abs = measure_abstinence_period(
            use_pattern_binary = udsPattern
        )
    ) %>% 
    select(who, schottenfeld2005_abs) %>% 
    left_join(outcomesAbs_df, ., by = "who")


outcomesAbs_df %>% 
  filter(who %in% examplePeople_int) %>% 
  select(who, usePatternUDS, schottenfeld2005_abs)

@schottenfeld_maintenance_2008 (A) and (B)

There are two definitions from this paper which we include in the reduction section our library: r defns_char["Schottenfeld et al., 2008"] and r defns_char["Schottenfeld, Chawarski, & Mazlan, 2008"].

Schottenfeld, Chawarski, & Mazlan, 2008 (A)

Definition: r defns_char["Schottenfeld, Chawarski, & Mazlan, 2008"], missing is positive

outcomesAbs_df <- 
    outcomesAbs_df %>%
  rowwise() %>% 
  mutate(
        udsPattern = recode_missing_visits(
            use_pattern = usePatternUDS
        )
    ) %>% 
    mutate(
        udsPattern = recode_missing_visits(
            use_pattern = udsPattern,
            missing_is = "*"
        )
    ) %>% 
    mutate(
        schottenfeld2008A_abs = detect_in_window(
            use_pattern = udsPattern,
            window_width = 1L,
            threshold = 1L
        )
    ) %>% 
    unnest(cols = "schottenfeld2008A_abs", names_sep = "_") %>% 
    select(who, starts_with("schottenfeld2008A_abs")) %>% 
    left_join(outcomesAbs_df, ., by = "who")

outcomesAbs_df %>% 
  filter(who %in% examplePeople_int) %>% 
  select(who, usePatternUDS, starts_with("schottenfeld2008A_abs"))

Schottenfeld et al., 2008 (B)

Definition: r defns_char["Schottenfeld et al., 2008"], missing is positive

outcomesAbs_df <- 
    outcomesAbs_df %>%
  rowwise() %>% 
  mutate(
        udsPattern = recode_missing_visits(
            use_pattern = usePatternUDS
        )
    ) %>% 
    mutate(
        udsPattern = recode_missing_visits(
            use_pattern = udsPattern,
            missing_is = "*"
        )
    ) %>%
    mutate(
        schottenfeld2008B_abs = measure_abstinence_period(
            use_pattern_binary = udsPattern
        )
    ) %>% 
    select(who, schottenfeld2008B_abs) %>% 
    left_join(outcomesAbs_df, ., by = "who")


outcomesAbs_df %>% 
  filter(who %in% examplePeople_int) %>% 
  select(who, usePatternUDS, schottenfeld2008B_abs)

@shufman_efficacy_1994

Definition: r defns_char["Shufman et al., 1994"], missing is ignored (but treated as negative in order to count the weeks properly)

outcomesAbs_df <- 
    outcomesAbs_df %>%
  rowwise() %>% 
  # Set "o" to "-"
    mutate(
        udsPattern = recode_missing_visits(
            use_pattern = usePatternUDS,
            missing_becomes = "-"
        )
    ) %>% 
    # Set "*" to "+"
    mutate(
        udsPattern = recode_missing_visits(
            use_pattern = udsPattern,
            missing_is = "*"
        )
    ) %>% 
    mutate(
        shufman1994_absN = detect_in_window(
            use_pattern = udsPattern,
            window_width = 1L,
            threshold = 1L
        )
    ) %>% 
    unnest(cols = "shufman1994_absN", names_sep = "_") %>% 
    select(who, starts_with("shufman1994_absN")) %>% 
    left_join(outcomesAbs_df, ., by = "who")


outcomesAbs_df %>% 
  filter(who %in% examplePeople_int) %>% 
  select(who, usePatternUDS, starts_with("shufman1994_absN"))

@weiss_adjunctive_2011 [CTN-0030]

Definition: r defns_char["Weiss et al., 2011 CTN-0030"], missing is positive.

Note: this definition is looking for one of the following four abstinence patterns in last 4 weeks: "----", "+---", "-+--", or "--+-". This definition is just an insanely strict measure of study retention. The first part of the definition ("negative in the last week") already fails anyone who didn't stay in the study for the entire protocol period (because their last week UDS will automatically be "o").

outcomesAbs_df <- 
    outcomesAbs_df %>%
  rowwise() %>% 
  mutate(
        udsPattern = recode_missing_visits(
            use_pattern = usePatternUDS,
        )
    ) %>% 
    mutate(
        udsPattern = recode_missing_visits(
            use_pattern = udsPattern,
            missing_is = "*"
        )
    ) %>% 
    mutate(
        cleanLastWeek = detect_subpattern(
            use_pattern = udsPattern,
            subpattern = "-",
            start = -1,
            end = -1
        )
    ) %>% 
    mutate(
        finalUseCount = count_matches(
            use_pattern = udsPattern,
            match_is = "+",
            # 3 weeks leading up to the last week
            start = -4L,
            end = -2L
        )
    ) %>% 
    mutate(weissLingCTN0030_isAbs = cleanLastWeek & (finalUseCount <= 1)) %>% 
    select(who, weissLingCTN0030_isAbs) %>% 
    left_join(outcomesAbs_df, ., by = "who")


outcomesAbs_df %>% 
  filter(who %in% examplePeople_int) %>% 
  select(who, usePatternUDS, weissLingCTN0030_isAbs)


Computing Environment

Here is the information concerning the system configuration, packages, and their versions used in this computation:

sessionInfo()
# write_csv(
#   outcomesAbs_df,
#   file = "../inst/extdata/outcomes_abstinence_20220818.csv"
# )

References



Try the CTNote package in your browser

Any scripts or data that you put into this service are public.

CTNote documentation built on Oct. 3, 2022, 9:08 a.m.