Transplant Cost Analysis
1 Study Details
1.1 Background
Tacrolimus is an immunosuppressant used to prevent organ rejection in kidney transplant recipients. Tacrolimus can be dosed using twice-daily immediate release capsules (Prograf, IR-Tac) or once-daily extended-release tablets (Envarsus XR, LCPT). LCPT has been shown to have a better side effect profile than IR-Tac but may be associated with higher costs since it is only available as a brand name medication. It can be more time consuming for pharmacy and clinical staff to navigate payment barriers since some insurance companies are hesitant to approve LCPT due to higher costs compared to IR-Tac, which frequently leads to prescriber hesitancy to change a patient to LCPT.
1.2 Methods
This was a single-center, retrospective review of pharmacy records and billing claims records from 1/1/2021-7/01/2022. Kidney transplant recipients who filled prescriptions written for IR-Tac or LCPT from Vanderbilt Transplant Pharmacy and were written by a provider at the Vanderbilt adult renal transplant clinic were included. Patients below 18 years of age and fills with MAP primary insurance plans were excluded.
1.3 Outcomes of interest
- OOP costs compared between Envarsus and IR-Tac while adjusting for age, race, gender, and primary insurance
2 Descriptive Statistics
2.1 Patient level characteristics
<- summaryM(Age_first_sold_date + gender + race + ethnicity+ number_fills + total_member_paid_scaled +
tab+ secondary_ins_cat+
primary_ins_cat ~ drugs , data=erx_pat, na.include=T,overall=T,
drug_order continuous = 1)
::html(tab,na.include=TRUE,long=T,exclude1=F, digits=2,prmsd=T,
Hmisccaption= "Patient level characteristics")
Patient level characteristics. | ||||
TACROLIMUS N=1314 |
ENVARSUS N=134 |
Both N=39 |
Combined N=1487 |
|
---|---|---|---|---|
Age at first sold date | 42 54 63  (53 ±13) | 43 52 65  (53 ±13) | 49 55 67  (57 ±12) | 43 54 63  (53 ±13) |
Gender | ||||
  Female | 0.45 (596) | 0.44 ( 59) | 0.49 ( 19) | 0.45 (674) |
  Male | 0.55 (718) | 0.56 ( 75) | 0.51 ( 20) | 0.55 (813) |
Race | ||||
  White | 0.59 (778) | 0.36 ( 48) | 0.62 ( 24) | 0.57 (850) |
  Black or African American | 0.35 (463) | 0.61 ( 82) | 0.33 ( 13) | 0.38 (558) |
  Other | 0.05 ( 65) | 0.02 ( 3) | 0.05 ( 2) | 0.05 ( 70) |
  NA | 0.01 ( 8) | 0.01 ( 1) | 0.00 ( 0) | 0.01 ( 9) |
Ethnicity | ||||
  Mexican, Mexican American, or Chicano/a | 0.01 ( 8) | 0.01 ( 2) | 0.03 ( 1) | 0.01 ( 11) |
  Not Hispanic, Latino/a, or Spanish origin | 0.95 (1242) | 0.95 ( 127) | 0.95 ( 37) | 0.95 (1406) |
  Other Hispanic, Latino/a, or Spanish origin | 0.03 ( 39) | 0.01 ( 1) | 0.03 ( 1) | 0.03 ( 41) |
  Puerto Rican | 0.00 ( 1) | 0.01 ( 1) | 0.00 ( 0) | 0.00 ( 2) |
  NA | 0.02 ( 24) | 0.02 ( 3) | 0.00 ( 0) | 0.02 ( 27) |
Number of fills | 6.0 11.0 17.0  (12.2 ± 8.2) | 6.0 14.0 18.0  (13.9 ± 8.9) | 8.0 12.0 15.5  (12.0 ± 5.7) | 6.0 12.0 18.0  (12.4 ± 8.3) |
Total member paid scaled by number of fills | 0.0 0.0 10.0  (10.4 ±26.4) | 0.0 0.0 0.0  ( 6.4 ±31.0) | 0.0 0.0 11.5  ( 8.1 ±14.0) | 0.0 0.0 9.2  (10.0 ±26.6) |
Most often filled primary insurance | ||||
  Commercial | 0.26 ( 346) | 0.21 ( 28) | 0.23 ( 9) | 0.26 ( 383) |
  Medicaid | 0.04 ( 48) | 0.05 ( 7) | 0.00 ( 0) | 0.04 ( 55) |
  Medicare | 0.70 ( 915) | 0.74 ( 99) | 0.74 ( 29) | 0.70 (1043) |
  Tricare | 0.00 ( 5) | 0.00 ( 0) | 0.03 ( 1) | 0.00 ( 6) |
Fills with any secondary insurance | ||||
  All | 0.18 (243) | 0.18 ( 24) | 0.00 ( 0) | 0.18 (267) |
  Some | 0.31 (402) | 0.41 ( 55) | 0.54 ( 21) | 0.32 (478) |
  None | 0.51 (669) | 0.41 ( 55) | 0.46 ( 18) | 0.50 (742) |
Order of drugs | ||||
  TAC to ENV | 0.00 ( 0) | 0.00 ( 0) | 1.00 ( 39) | 0.03 ( 39) |
  NA | 1.00 (1314) | 1.00 ( 134) | 0.00 ( 0) | 0.97 (1448) |
a b c represent the lower quartile a, the median b, and the upper quartile c for continuous variables. x ± s represents X ± 1 SD. Numbers after proportions are frequencies. |
2.2 Fill Level Characteristics
<- summaryM(primary_plan_cat +
tab+
secondary_plan +
member_paid +
primary_paid +
secondary_paid ~ drug , data=erx, na.include=T,overall=F,
total_paid continuous = 1)
::html(tab,na.include=TRUE,long=T,exclude1=F, digits=2,prmsd=T,
Hmisccaption= "Fill level characteristics")
Fill level characteristics. | |||
N |
TACROLIMUS N=16262 |
ENVARSUS N=2149 |
|
---|---|---|---|
Primary insurance plan | 18411 | ||
  Commercial | 0.25 ( 4008) | 0.13 ( 269) | |
  Medicaid | 0.03 ( 488) | 0.04 ( 81) | |
  Medicare | 0.72 (11714) | 0.83 ( 1785) | |
  Tricare | 0.00 ( 52) | 0.01 ( 14) | |
Secondary insurance plan | 18411 | ||
  CAID | 0.02 ( 404) | 0.04 ( 91) | |
  COCH | 0.18 ( 3005) | 0.20 ( 430) | |
  COFD | 0.00 ( 5) | 0.00 ( 0) | |
  COOP/BCBS | 0.05 ( 805) | 0.07 ( 146) | |
  COSP | 0.00 ( 6) | 0.00 ( 0) | |
  GOV | 0.00 ( 63) | 0.02 ( 33) | |
  MCPB | 0.04 ( 664) | 0.00 ( 0) | |
  MCRX | 0.00 ( 24) | 0.00 ( 2) | |
  NA | 0.69 (11286) | 0.67 ( 1447) | |
Member paid | 18411 | 0.0 0.0 3.9  ( 8.1 ±26.2) | 0.0 0.0 0.0  ( 5.2 ±33.0) |
Primary paid | 18411 | 33 57 112  (101 ±150) | 281 531 927  (769 ±820) |
Secondary paid | 5664 | 10 17 34  ( 36 ± 75) | 41 109 235  (181 ±214) |
Total paid | 18411 | 43 69 126  ( 120 ± 168) | 289 545 1033  ( 833 ± 899) |
a b c represent the lower quartile a, the median b, and the upper quartile c for continuous variables. x ± s represents X ± 1 SD.   N is the number of non-missing values. Numbers after proportions are frequencies. |
2.3 Costs by insurance category
2.3.1 Mean costs
$map_cat <- ifelse(is.na(erx$secondary_insurance), "Primary Insurance Only",
erxifelse(erx$MAP_applied == "Yes", "Primary Insurance + MAP", "Primary Insurance + Non-MAP"))
$map_cat <- factor(erx$map_cat, levels=c("Primary Insurance Only",
erx"Primary Insurance + MAP",
"Primary Insurance + Non-MAP"))
<- function(x,r){
print_res_mean paste0(round(mean(x),r), " (" ,round(sd(x),r),")")
}<- function(x,r){
print_res_iqr paste0(round(median(x),r), " (" ,round(quantile(x)[2],r),", ",round(quantile(x)[4],r),")")
}
<- as.data.frame(matrix(NA, ncol=4, nrow=3))
res for(i in 1:lun(erx$map_cat)){
for(j in 1:4){
<- unique(erx$map_cat)[i]
map_cat <- c("member_paid","primary_paid", "secondary_paid","total_paid")[j]
cost <- erx[erx$map_cat == map_cat,cost]
tmp if(mean(is.na(tmp))==1){
<-""
res[i,j] else{
}<- print_res_mean(tmp,2)
res[i,j]
}
}
}rownames(res) <- unique(erx$map_cat)
colnames(res) <- c("Member Paid","Primary Paid", "Secondary Paid","Total Paid")
#res[3,3] <- ""
$n <- NA
resfor(i in 1:3){
$n[i] <- sum(erx$map_cat == unique(erx$map_cat)[i])
res
}<- res
overall
<- c("TACROLIMUS" , "ENVARSUS")
drugs <- list()
list_res for(k in 1:2){
<- as.data.frame(matrix(NA, ncol=4, nrow=3))
res for(i in 1:lun(erx$map_cat)){
for(j in 1:4){
<- drugs[k]
drug <- unique(erx$map_cat)[i]
map_cat <- c("member_paid","primary_paid", "secondary_paid","total_paid")[j]
cost <- erx[erx$map_cat == map_cat & erx$drug == drug,cost]
tmp if(mean(is.na(tmp))==1){
<-""
res[i,j] else{
}<- print_res_mean(tmp,2)
res[i,j]
}
}
}rownames(res) <- unique(erx$map_cat)
colnames(res) <- c("Member Paid","Primary Paid", "Secondary Paid","Total Paid")
#res[3,3] <- ""
$n <- NA
resfor(i in 1:3){
$n[i] <- sum(erx$map_cat == unique(erx$map_cat)[i] & erx$drug == drug)
res
}<- res
list_res[[k]]
}names(list_res) <- drugs
<- rbind(overall, list_res[[1]])
all_res <- rbind(all_res, list_res[[2]])
all_res $`Insurance Category` <- rep(c("Primary Insurance + MAP", "Primary Insurance + Non-MAP", "Primary Insurance Only"),3)
all_resrownames(all_res) <- NULL
%>%
all_res select(`Insurance Category`, `Member Paid`, `Primary Paid`, `Secondary Paid`, n) %>%
kable(caption="Mean (SD) costs of fills by insurance category and drug") %>%
pack_rows("Overall", 1,3) %>%
pack_rows("TACROLIMUS",4,6) %>%
pack_rows("ENVARSUS",7,9) %>%
kable_classic_2()
Insurance Category | Member Paid | Primary Paid | Secondary Paid | n |
---|---|---|---|---|
Overall | ||||
Primary Insurance + MAP | 0 (0) | 197.84 (445.69) | 53.42 (114.84) | 3423 |
Primary Insurance + Non-MAP | 0.01 (0.33) | 151.39 (341.34) | 55.56 (110.49) | 2241 |
Primary Insurance Only | 11.22 (31.93) | 179.05 (366.66) | 12747 | |
TACROLIMUS | ||||
Primary Insurance + MAP | 0 (0) | 106.2 (177.85) | 32.74 (59.84) | 2996 |
Primary Insurance + Non-MAP | 0.01 (0.36) | 84.58 (111.22) | 42.1 (92.63) | 1969 |
Primary Insurance Only | 11.67 (30.73) | 102.85 (148.24) | 11297 | |
ENVARSUS | ||||
Primary Insurance + MAP | 0 (0) | 840.84 (948.56) | 198.53 (238.03) | 427 |
Primary Insurance + Non-MAP | 0 (0) | 634.98 (778.51) | 152.96 (166.59) | 272 |
Primary Insurance Only | 7.66 (39.9) | 772.73 (783.13) | 1450 |
2.3.2 Median costs
<- as.data.frame(matrix(NA, ncol=4, nrow=3))
res for(i in 1:lun(erx$map_cat)){
for(j in 1:4){
<- unique(erx$map_cat)[i]
map_cat <- c("member_paid","primary_paid", "secondary_paid","total_paid")[j]
cost <- erx[erx$map_cat == map_cat,cost]
tmp if(mean(is.na(tmp))==1){
<-""
res[i,j] else{
}<- print_res_iqr(tmp,2)
res[i,j]
}
}
}rownames(res) <- unique(erx$map_cat)
colnames(res) <- c("Member Paid","Primary Paid", "Secondary Paid","Total Paid")
#res[3,3] <- ""
$n <- NA
resfor(i in 1:3){
$n[i] <- sum(erx$map_cat == unique(erx$map_cat)[i])
res
}<- res
overall
<- c("TACROLIMUS" , "ENVARSUS")
drugs <- list()
list_res for(k in 1:2){
<- as.data.frame(matrix(NA, ncol=4, nrow=3))
res for(i in 1:lun(erx$map_cat)){
for(j in 1:4){
<- drugs[k]
drug <- unique(erx$map_cat)[i]
map_cat <- c("member_paid","primary_paid", "secondary_paid","total_paid")[j]
cost <- erx[erx$map_cat == map_cat & erx$drug == drug,cost]
tmp if(mean(is.na(tmp))==1){
<-""
res[i,j] else{
}<- print_res_iqr(tmp,2)
res[i,j]
}
}
}rownames(res) <- unique(erx$map_cat)
colnames(res) <- c("Member Paid","Primary Paid", "Secondary Paid","Total Paid")
#res[3,3] <- ""
$n <- NA
resfor(i in 1:3){
$n[i] <- sum(erx$map_cat == unique(erx$map_cat)[i] & erx$drug == drug)
res
}<- res
list_res[[k]]
}names(list_res) <- drugs
<- rbind(overall, list_res[[1]])
all_res <- rbind(all_res, list_res[[2]])
all_res $`Insurance Category` <- rep(c("Primary Insurance + MAP", "Primary Insurance + Non-MAP", "Primary Insurance Only"),3)
all_resrownames(all_res) <- NULL
%>%
all_res select(`Insurance Category`, `Member Paid`, `Primary Paid`, `Secondary Paid`, n) %>%
kable(caption="Median (IQR) costs of fills by insurance category and drug") %>%
pack_rows("Overall", 1,3) %>%
pack_rows("TACROLIMUS",4,6) %>%
pack_rows("ENVARSUS",7,9) %>%
kable_classic_2()
Insurance Category | Member Paid | Primary Paid | Secondary Paid | n |
---|---|---|---|---|
Overall | ||||
Primary Insurance + MAP | 0 (0, 0) | 63.24 (33.59, 149.12) | 16.86 (9.43, 47.16) | 3423 |
Primary Insurance + Non-MAP | 0 (0, 0) | 60.17 (32.86, 135.65) | 22.64 (11.63, 49.86) | 2241 |
Primary Insurance Only | 0 (0, 10) | 70 (39.1, 152.35) | 12747 | |
TACROLIMUS | ||||
Primary Insurance + MAP | 0 (0, 0) | 50.14 (32.44, 105.36) | 15.66 (8.6, 33.91) | 2996 |
Primary Insurance + Non-MAP | 0 (0, 0) | 46.51 (32.44, 101.14) | 18.46 (11.39, 34.08) | 1969 |
Primary Insurance Only | 0 (0, 11.04) | 58.14 (36.36, 115.59) | 11297 | |
ENVARSUS | ||||
Primary Insurance + MAP | 0 (0, 0) | 449.12 (286.17, 1133.36) | 110.71 (69.39, 283.34) | 427 |
Primary Insurance + Non-MAP | 0 (0, 0) | 421.76 (163.1, 602.9) | 105.44 (39.94, 151.92) | 272 |
Primary Insurance Only | 0 (0, 0) | 544.84 (288.42, 925) | 1450 |
3 Regression analysis of OOP costs
The distribution of member paid is extremely skewed and has many values of 0, making it difficult to model with ordinary least squares regression so an ordinal regression model is used.
A mixed effects model would be ideal to account for correlation within subjects, but an abundance of repeating values of the outcome (and over-saturation of 0s) caused the the model to have difficulties converging.
Instead the total OOP costs per patient scaled by the number of fills was used as the outcome (mean OOP cost) for an ordinal logistic regression model. And ordinal model was used to account for the severe skew in the distribution, which would violate the assumptions of ordinary least squares regression.
Every fill where a patient has secondary insurance results in an OOP expense of 0 so it was excluded from the model.
$total_member_paid_scaled <- erx_pat$total_member_paid/erx_pat$number_fills
erx_pat<- datadist(erx_pat[,c("total_member_paid_scaled","total_member_paid", "race", "gender",
ddd "Age_first_sold_date","number_fills","drugs",
"mult_strength", "primary_ins_cat", "secondary_ins_cat")])
options(datadist="ddd") #often a good idea, to store info with fit
<- orm(total_member_paid_scaled ~ drugs + I(Age_first_sold_date/10) +race +gender+ primary_ins_cat , data=erx_pat) fit
<- data.frame(Variable = grep('y>',names(coef(fit)),inv=T, v = T),
res lor = coef(fit)[grep('y>',names(coef(fit)),inv=T, v = T)],
rse =sqrt(diag(vcov(fit)))[grep('y>',names(coef(fit)),inv=T, v = T)]) %>%
mutate(lower = (lor - 1.96*rse), upper = (lor + 1.96*rse),
`p-value` = format.pval( round(pchisq((lor/rse)^2,1, lower.tail = FALSE),4), digits = 3, eps = 0.001)) %>%
mutate(`Log Odds` = lor, `Odds` = exp(lor), Lower = exp(lower), Upper = exp(upper)) %>%
select(Variable, `Log Odds`,`Odds`, Lower, Upper, `p-value`)
$Variable <- factor(res$Variable,
reslevels=res$Variable,
labels=c("Envarsus",
"Both medications",
"at first sold date and by 10 years",
"Black or African American",
"Other",
"Male",
"Medicaid ",
"Medicare ",
"Tricare "
))
rownames(res) <- NULL
<- res
hold $Visualization <- ""
res$odds_numeric <- res$Odds
res$Odds <- paste0(round(res$Odds, 2), " (", round(res$Lower, 2)," ,", round(res$Upper, 2),")")
res
%>%
res select(1, 3,6, Visualization) %>%
rename("Odds Ratio (95% CI)" = Odds) %>%
#arrange(Variable) %>%
kbl(booktabs = T) %>%
kable_classic(full_width = T ) %>%
pack_rows("Drug (Tacrolimus reference)", 1,2) %>%
pack_rows("Age",3,3)%>%
pack_rows("Race (White reference)",4,5)%>%
pack_rows("Gender (Female reference)",6,6)%>%
pack_rows("Primary Insurance (Commercial reference)", 7, 9) %>%
column_spec(4, image=spec_pointrange(x=res$odds_numeric, xmin=res$Lower, xmax=res$Upper, vline=1))
Variable | Odds Ratio (95% CI) | p-value | Visualization |
---|---|---|---|
Drug (Tacrolimus reference) | |||
Envarsus | 0.16 (0.09 ,0.3) | <0.001 | |
Both medications | 1.19 (0.63 ,2.24) | 0.589 | |
Age | |||
at first sold date and by 10 years | 0.98 (0.9 ,1.07) | 0.713 | |
Race (White reference) | |||
Black or African American | 1.08 (0.86 ,1.36) | 0.517 | |
Other | 1.23 (0.76 ,2) | 0.406 | |
Gender (Female reference) | |||
Male | 1.26 (1.02 ,1.57) | 0.036 | |
Primary Insurance (Commercial reference) | |||
Medicaid | 0.22 (0.13 ,0.38) | <0.001 | |
Medicare | 0.17 (0.14 ,0.22) | <0.001 | |
Tricare | 2.5 (0.76 ,8.26) | 0.133 |