R/calculate_mgfr_2c.R

Defines functions calculate_mgfr_2c

Documented in calculate_mgfr_2c

#' Iohexol measured GFR with 2-compartment model (early and late sampling)
#'
#' This method requires multiple early (alpha phase, typically <120 min) and
#' late (beta phase) sample measurements of iohexol to determine GFR (glomerular
#' filtration rate). This function uses nonlinear modeling via
#' Levenberg-Marquardt method in the `gsl_nsl` package and the `gslnls()`
#' function to fit the 2-compartment model of iohexol kinetics. The NLLS methods use the formula \eqn{C=A \cdot exp^{- \alpha \cdot t} +
#' B*e^{- \beta \cdot t} } and weighted by \eqn{1/Iohexol^2}. Initial estimates for early (A, a) and late (B, b) parameters used for NLLS estimation are obtained using standard "Slope-Intercept" methods for linear regresion of \eqn{log(Iohexol)~time} for two lines for the early and late portions. Results for this model can be obtained using the `nls_v="SI"` option. Use of the `gsl_nsl` package allows for better support of parameter constraints (constrained to positive values) and other methods (e.g. `predict` methods using fit object) compared to the base `nls()` function.  The general method and provided examples are
#' modified from \href{https://pubmed.ncbi.nlm.nih.gov/16612328/}{Schwartz et al.
#' Kidney Int. 2006.}. Further discussion and details regarding methods of fitting the 2-compartment models can be found at \href{https://www.ncbi.nlm.nih.gov/pmc/articles/PMC8101203/}{Pottel et al.} Results are returned as a non-indexed value for GFR (`mgfr_2c`) and also indexed to BSA of 1.73m2 if height and weight are provided (`mgfr_2c_bsa`).
#' The summary provides estimates of kinetic parameters (k10, k21, k12) which
#' can be used in ODE models (see example in [compare_mgfr_summary]), but is not
#' implemented here.
#'
#' @param time A vector of time values (minutes)
#' @param iohexol_conc A vector of Iohexol plasma measurements (ug/mL)
#' @param omnipaque_v Omnipaque version (eg 300 = Omnipaque 300 recommended).
#'   Other versions commonly available include 300 and 350 (USA) and 140, 280,
#'   240 (other areas)
#' @param ioh_inj_wt Iohexol injection Weight by syringe weight determination
#'   (Pre-Post weight difference; recommnded method) in grams
#' @param ioh_inj_vol Iohexol injection Volume by syringe volume injected (Not
#'   preferred; use if weights not available)
#' @param height Patient Height, m
#' @param weight Patient Weight, kg
#' @param height_units Height units, if not in m
#' @param weight_units Weight units, if not in kg
#' @param ioh_units Iohexol concentration units, defaults to `ug/mL`
#' @param id Study identifier (optional, passed to plot title)
#' @param time_units Time units, defaults to `min`
#' @param t_early Last Time point (minutes) to use in the "Early" distribution
#'   phase, defaults to `100`
#' @param t_late First Time point (minutes) to use in the "Late" elimination
#'   phase, defaults to `120`
#' @param nls_v Model estimation method (defaults to `gslnls`).  Can also use
#'   `SI`, `base`, or `MSP`. If `MSP` is selected, only "late" time points will
#'   be used and "early" time points will be discarded. If no early timepoints
#'   are available, method defaults to `MSP`.
#' @param nls_weights Use weights in nls model (`TRUE` or `FALSE`), defaults to
#'   `TRUE`. Uses 1/iohexol^2 as weights, which more heavily weight lower
#'   concentrations obtained at later time points
#' @param legend_cex Magnification factor for plot text, default 1.0 (100%)   
#' @param output Desired output, defaults to `summary` of model. Alternatively
#'   can specify `gfr`, `gfr_bsa`, `fit`, or `plot`
#'
#'   The `mgfr_method` describes the method for estimation, which can have the
#'   following options:
#'    * `SI` Slope-Intercept method obtained by performing two linear regressions of log(iohexol) vs time for early and late time points. Obtained by setting `nls_v = "SI"`
#'    * `modified-SI` SI method with shared time-point(s) between early and late periods (ie overlapping `t_early` = `t_late`). Obtained by setting, for example `nls_v = "SI", t_early=120, t_late=120` or another option with overlapping times.
#'    * `NLLS-gslnls-weighted` (Default method) NLS fit using the `gslnls()` function from the `gsl_nls` package. Obtained by setting `nls_v = "gslnls", nls_weights=T`
#'    * `NLLS-base-weighted` NLS fit using the `nls()` function from the base `stats` package. Obtained by setting `nls_v = "base", nls_weights=T`. Similar to `gslnls` method, except that parameter constraints cannot be set with `nls()`, and so implausible estimates (negative for a, b) may be obtained.
#'    * `NLLS-gslnls-unweighted` NLS fit using the `gslnls()` function from the `gsl_nls` package. Obtained by setting `nls_v = "gslnls", nls_weights=F`
#'    * `NLLS-base-unweighted` NLS fit using the `nls()` function from the base `stats` package. Obtained by setting `nls_v = "base", nls_weights=F`. This method weights heavily to early time points (higher concentration). Also, parameter constraints cannot be set with `nls()`, and so implausible estimates (negative for a, b) may be obtained.
#'    * `MSP-BM` The Multiple Sample Protocol (MSP), or Multiple Late Sample (MLS) estimate using 1-compartment analysis of Late time points only (>120 minutes post injection). Adjustment made using the Brochner-Mortenson correction equation to correct for unmeasured early time points.
#'    * `SS-Jacobbson` Single Sample estimate of GFR
#'
#'   For `output = "summary"` results returned includes the named variables:
#'    * `mgfr_method` Method used for GFR calculation (see above). Results should be similar.
#'    * `mgfr_2c` Measured GFR (mL/min)
#'    * `mgfr_2c_bsa` The measured GFR (mL/min/1.73 m2) indexed to body surface area (BSA calculated by DuBois equation)
#'    * `iohexol_m`   Iohexol mass (ucg) injected. Determined either by user-provided syringe weight or volume injected.
#'    * `iohexol_0`   Iohexol concentration at t=0 calculated by A+B (theoretical, not measured)
#'    * `iohexol_vd`  Iohexol Volume of distribution estimated by Dose/Iohexol[t0]
#'    * `ioh_auc`  Iohexol calculated AUC from t=0 to Infinity, estimated by A/a + B/b.
#'    * `A` Model parameter A
#'    * `a` Model parameter a, or \alpha
#'    * `B` Model parameter B
#'    * `b` Model parameter b, or \beta
#'    * `model_r2` Model Pseudo-R2, calculated as the linear regression R2 for predicted~observed values
#'    * `ssr` Sum of squared residuals
#'    * `sse` Sum of squared residuals for early time points
#'    * `ssl` Sum of squared residuals for late time points
#'    * `k10` Iohexol elimination rate constant from central compartment (1/min)
#'    * `k21` Iohexol transfer rate constant from peripheral to central compartment (1/min)
#'    * `k12` Iohexol transfer rate constant from central to peripheral compartment (1/min)
#'    * `n_early` Number of early time points measured and used in model
#'    * `n_late` Number of late time points measured and used in model
#'
#'   `gfr` returns a single `mgfr_2c` value, the measured GFR (mL/min).
#'   `gfr_bsa` returns a single `mgfr_2c_bsa` value, the measured GFR
#'   (mL/min/1.73 m2) indexed to body surface area (BSA calculated by DuBois
#'   equation).
#'
#'   `fit` returns the non-linear regression model fit object.
#'
#'   `plot` returns a base-R plot of observed and predicted regression curve vs
#'   time, and summary measures in the legend.
#'
#' @return Desired output with either a data.frame of results (`summary`), a
#'   single value of BSA adjusted GFR (`gfr` or `gfr_bsa`), an `nls object` for
#'   model fit (`fit`), or a plot with observed values, model fit curve, and
#'   summary results in the figure legend (`plot`)
#' @export calculate_mgfr_2c
#' @importFrom gslnls gsl_nls
#' @md
#' @seealso [compare_mgfr_summary()] for quick Table of results calculated by
#'   available methods. [compare_mgfr_plot()] for quick Visual comparison of
#'   plots calculated by available methods.
#'
#' @examples
#' library(tabletools)
#' # PUBLISHED IOHEXOL DATA ----
#' # data available in package by calling
#' ## ├ Schwartz Data ----
#' # Data extracted from Schwartz Fig1 https://pubmed.ncbi.nlm.nih.gov/16612328/
#' # Iohexol 5mL IV injection (Omnipaque 300, 5mL ~3235mg Iohexol)
#' # sampling at 10, 20, 30, 60, 120, 240, 300, 360 min
#' # time (minutes)
#' # Iohexol (ug/ml)
#' # age, height, weight not known for the example
#' dat_schwartz
#'
#' ## ├ Pottel data ----
#' # from Supplemental document in https://pubmed.ncbi.nlm.nih.gov/33952185/
#' dat10
#' dat17
#'
#' ## ├ Tondel data ----
#' # full example data provided by Tondel in Table 2: https://pubmed.ncbi.nlm.nih.gov/29134449/
#' dat_tondel
#'
#' # Comparison of Output options
#' calculate_mgfr_2c(dat_schwartz$time, dat_schwartz$iohexol_ug_ml, height = 1.67, weight = 70, ioh_inj_vol = 5) # Default
#' calculate_mgfr_2c(dat_schwartz$time, dat_schwartz$iohexol_ug_ml, height = 1.67, weight = 70, ioh_inj_vol = 5, output="gfr")
#' calculate_mgfr_2c(dat_schwartz$time, dat_schwartz$iohexol_ug_ml, height = 1.67, weight = 70, ioh_inj_vol = 5, output="gfr_bsa")
#' calculate_mgfr_2c(dat_schwartz$time, dat_schwartz$iohexol_ug_ml, height = 1.67, weight = 70, ioh_inj_vol = 5, output="fit")
#' calculate_mgfr_2c(dat_schwartz$time, dat_schwartz$iohexol_ug_ml, height = 1.67, weight = 70, ioh_inj_vol = 5, output="fit", nls_v="base")
#' calculate_mgfr_2c(dat_schwartz$time, dat_schwartz$iohexol_ug_ml, height = 1.67, weight = 70, ioh_inj_vol = 5, output="fit", nls_v="SI") # two fits
#'
#' # Plot options: ideally pass the ID information
#' calculate_mgfr_2c(dat_schwartz$time, dat_schwartz$iohexol_ug_ml, height = 1.67, weight = 70, ioh_inj_vol = 5, output="plot", id="Name-IDnumber-Date")
#' calculate_mgfr_2c(dat_schwartz$time, dat_schwartz$iohexol_ug_ml, height = 1.67, weight = 70, ioh_inj_vol = 5, output="plot")
#' calculate_mgfr_2c(dat_schwartz$time, dat_schwartz$iohexol_ug_ml, height = 1.67, weight = 70, ioh_inj_vol = 5, output="plot", nls_v = "SI") # fit not found
#' calculate_mgfr_2c(dat_schwartz$time, dat_schwartz$iohexol_ug_ml, height = 1.67, weight = 70, ioh_inj_vol = 5, output="plot", nls_v = "SI", t_early = 120, t_late = 120)
#' calculate_mgfr_2c(dat_schwartz$time, dat_schwartz$iohexol_ug_ml, height = 1.67, weight = 70, ioh_inj_vol = 5, output="plot", nls_v = "gslnls")
#' calculate_mgfr_2c(dat_schwartz$time, dat_schwartz$iohexol_ug_ml, height = 1.67, weight = 70, ioh_inj_vol = 5, output="plot", nls_v = "base")
#' calculate_mgfr_2c(dat_schwartz$time, dat_schwartz$iohexol_ug_ml, height = 1.67, weight = 70, ioh_inj_vol = 5, output="plot", nls_v = "gslnls", nls_weights = F)
#' calculate_mgfr_2c(dat_schwartz$time, dat_schwartz$iohexol_ug_ml, height = 1.67, weight = 70, ioh_inj_vol = 5, output="plot", nls_v = "base", nls_weights = F)
#' calculate_mgfr_2c(dat_schwartz$time, dat_schwartz$iohexol_ug_ml, height = 1.67, weight = 70, ioh_inj_vol = 5, output="plot", legend_cex = 1.5)
#'
#' # examples with fewer time points
#' dat_5p <- dat_schwartz[dat_schwartz$time %in% c(10, 20, 30, 120, 300), ]
#' calculate_mgfr_2c(dat_5p$time, dat_5p$iohexol_ug_ml, height = 1.67, weight = 70, ioh_inj_vol = 5, output="plot")
#'
#' dat_5p <- dat_schwartz[dat_schwartz$time %in% c(10, 30, 60, 120, 300), ]
#' calculate_mgfr_2c(dat_5p$time, dat_5p$iohexol_ug_ml, height = 1.67, weight = 70, ioh_inj_vol = 5, output="plot")
#'
#' dat_7p <- dat_schwartz[dat_schwartz$time %in% c(10, 20, 30, 60, 240, 300, 360), ]
#' calculate_mgfr_2c(dat_7p$time, dat_7p$iohexol_ug_ml, height = 1.67, weight = 70, ioh_inj_vol = 5, output="plot")
#'
#' # if early time points not present, defaults to MSP-BM estimation (using `calculate_mgfr_msp` with warning)
#' dat_ebert
#' calculate_mgfr_2c(dat_ebert$time, dat_ebert$iohexol, height = 1.68, weight=87, ioh_inj_vol = 5.06)
#' calculate_mgfr_2c(dat_ebert$time, dat_ebert$iohexol, height = 1.68, weight=87, ioh_inj_vol = 5.06, output="plot", leg)
#' calculate_mgfr_msp(dat_ebert$time, dat_ebert$iohexol, height = 1.68, weight=87, ioh_inj_vol = 5.06)

calculate_mgfr_2c <- function(time, iohexol_conc, 
                              omnipaque_v=300, ioh_inj_vol=5.0,
                              ioh_inj_wt=NULL,  
                              ioh_units="ug/mL", time_units="min",
                              id=NULL,
                              height=NA, height_units = "m", 
                              weight=NA, weight_units = "kg",
                              nls_weights = TRUE, nls_v = "gslnls",
                              t_late = 120, t_early = 100,
                              legend_cex=1.0,
                              output="summary"){
  # Subject calcs
  bsa = ifelse(is.na(height) || is.na(weight), NA, 
               calculate_bsa(weight, height, height_units = height_units, method = "DuBois"))
  
  # Omnipaqe calcs; from lookup table stored internally as tabletools:::df_omnipaque
  # Note: standard 5mL injection of Iohexol iohexol_v = 5 # mL = 3235 mg Iohexol.
  # df_omnipaque=tabletools:::df_omnipaque # available in internal data
  ioh_spgrav = df_omnipaque$omnipaque_specgrav[df_omnipaque$omnipaque_v==omnipaque_v]/1000 # g/ml
  iohexol_mg_ml = df_omnipaque$iohexol_mg_ml[df_omnipaque$omnipaque_v==omnipaque_v] # mg/ml
  if (!is.null(ioh_inj_wt) & !is.null(ioh_inj_vol)) {
    ioh_inj_vol = ioh_inj_wt/ioh_spgrav
    rlang::warn("Iohexol injection weight is provided; `ioh_inj_vol` recalculated.")}
  if (is.null(ioh_inj_wt) & is.null(ioh_inj_vol)) stop("missing injection details; must provide `ioh_inj_wt` or `ioh_inj_vol`")
  if (!is.null(ioh_inj_wt)) {ioh_inj_vol = ioh_inj_wt/ioh_spgrav  } # vol calculated by weight
  iohexol_m = ioh_inj_vol*iohexol_mg_ml*1000 # mass mcg iohexol injected
  
  # Data: get time, iohexol data
  time_min =  convert_time_to_min(time, time_units)
  iohexol_valid = ifelse(tolower(ioh_units) %in% c("ug/ml","mcg/ml","mg/l"), T, NA)
  iohexol = iohexol_conc[iohexol_valid]
  
  # get initial guess from SI method
  dat = data.frame(time=time_min, iohexol=iohexol)
  wt <- if (nls_weights) {1/iohexol^2} else {rep(1, length(dat$iohexol))} # weights for NLLS
  
  # Late Timepoints 
  # t_late = 120
  dat_late <- dat[dat$time >= t_late, ]
  lm_late <- lm(log(iohexol) ~ time, data = dat_late)
  B_start <-  exp(coef(lm_late))[["(Intercept)"]]
  b_start <- -coef(lm_late)[["time"]]
  
  # Early Timepoints 
  # t_early = 100
  if (length(dat$time[dat$time<=t_early])==0 || tolower(nls_v) == "msp") {
    rlang::warn("No early timepoints detected. Using MSP calculation only.")
    res <- calculate_mgfr_msp(time = time_min, iohexol_conc = iohexol, 
                              omnipaque_v=omnipaque_v, 
                              ioh_inj_vol=ioh_inj_vol,
                              ioh_inj_wt=NULL,  
                              ioh_units="ug/mL", 
                              time_units="min",
                              id=id,
                              output = output,
                              height = height, height_units = height_units ,
                              weight=weight, weight_units = weight_units, legend_cex = legend_cex)
    return(res)
  } else { 
    dat_early <- dat[dat$time <= t_early, ]
    # subtract predicted from observed
    dat_early$pred <- exp(predict(lm_late, dat_early))
    dat_early$conc_adj <- dat_early$iohexol - dat_early$pred
    # if (any(dat_early$conc_adj <0)) {stop("negative concentration values observed")}
    lm_early <- lm(log(conc_adj) ~ time, data = dat_early)
    A_start <-  exp(coef(lm_early))[["(Intercept)"]]
    a_start <- -coef(lm_early)[["time"]]}
  
  if (tolower(nls_v) == "si"){
    A = A_start # use initial SI estimate
    a = a_start # use initial SI estimate
    B = B_start # use initial SI estimate
    b = b_start # use initial SI estimate
    
    mgfr_method = if (length(intersect(dat_early$time, dat_late$time))>0) {"modified-SI"} else {"SI"}
    AUC_inf <- A/a + B/b  # AUC_inf 
    mgfr_2c = iohexol_m/AUC_inf  # Dose/AUC_inf = mL/minutes (not indexed to BSA)
    mgfr_2c_bsa = ifelse(!is.na(bsa), mgfr_2c*1.73/bsa, NA)  # indexed to BSA
    iohexol_vd = iohexol_m/1000/(A+B) # volume of distribution, L
    
    # model fit parameters
    fit_SI_vals <- function(t, A, a, B, b) {A*exp(-a*t)+B*exp(-b*t) }
    dat$pred <- fit_SI_vals(dat$time, A, a, B, b)
    dat$resid <- dat$iohexol - dat$pred
    # ODE micro parameters:
    k10 = iohexol_m/iohexol_vd/AUC_inf/1000 # 1/min
    k21 = a*b/k10
    k12 = a+b - k10 - k21
    
    model_r2 =  summary(lm(pred ~ iohexol, data = dat))[["r.squared"]]  # this is a pseudo-r2
    ssr = sum(dat$resid^2)
    sse = sum(dat$resid[dat$time <= t_early]^2)
    ssl = sum(dat$resid[dat$time >= t_late]^2)
    res = data.frame("mgfr_method" = mgfr_method,
                     "mgfr_2c"     = mgfr_2c,
                     "mgfr_2c_bsa" = mgfr_2c_bsa,
                     "iohexol_m"   = iohexol_m,
                     "iohexol_0"   =  A+B,
                     "iohexol_vd"  = iohexol_vd,
                     "ioh_auc"     = AUC_inf,
                     "A"           = A,
                     "a"           = a,
                     "B"           = B,
                     "b"           = b,
                     "model_r2"    = model_r2, # this is a pseudo-r2
                     "ssr"         = ssr,
                     "sse"         = sse,
                     "ssl"         = ssl,
                     "k10"         = k10,
                     "k21"         = k21,
                     "k12"         = k12,
                     "n_early"     = length(dat_early$time),
                     "n_late"     = length(dat_late$time))
    
  }  
  #______________________________________________  
  if (tolower(nls_v) == "gslnls") {
    # 2C NLS model fit
    fit = gslnls::gsl_nls(iohexol ~ A*exp(-a*time_min) + B*exp(-b*time_min),
                          data=data.frame(time_min, iohexol),
                          lower = c(A=0, B=0, a=0, b=0),
                          weights = wt,
                          start=list(A = A_start,
                                     B = B_start,
                                     a = a_start,
                                     b = b_start))  
    
    # Model Results
    A  = coef(fit)[["A"]]
    a  = coef(fit)[["a"]]
    B  = coef(fit)[["B"]]
    b  = coef(fit)[["b"]]
    AUC_inf <- A/a + B/b  # AUC_inf 
    mgfr_2c = iohexol_m/AUC_inf  # Dose/AUC_inf = mL/minutes (not indexed to BSA)
    mgfr_2c_bsa = ifelse(!is.na(bsa), mgfr_2c*1.73/bsa, NA)  # indexed to BSA
    iohexol_vd = iohexol_m/1000/(A+B) # volume of distribution, L
    # ODE micro parameters:
    k10 = iohexol_m/iohexol_vd/AUC_inf/1000 # 1/min
    k21 = a*b/k10
    k12 = a+b - k10 - k21
    # model fit parameters
    model_r2 = summary(lm(iohexol ~ predict(fit)))[["r.squared"]]   # this is a pseudo-r2
    ssr = sum(residuals(fit)^2)
    sse = sum(residuals(fit)[time_min<=t_early]^2)
    ssl = sum(residuals(fit)[time_min>=t_late]^2)
    mgfr_method =
      if (nls_weights & tolower(nls_v)=="gslnls") {"NLLS-gslnls-weighted"
      } else if (nls_weights & tolower(nls_v)=="base") {"NLLS-base-weighted"
      } else if (!nls_weights & tolower(nls_v)=="base") {"NLLS-base-unweighted"
      } else if (!nls_weights & tolower(nls_v)=="gslnls") {"NLLS-gslnls-unweighted"}
    res = data.frame("mgfr_method" = mgfr_method,
                     "mgfr_2c"     = mgfr_2c,
                     "mgfr_2c_bsa" = mgfr_2c_bsa,
                     "iohexol_m"   = iohexol_m,
                     "iohexol_0"   =  A+B,
                     "iohexol_vd"  = iohexol_vd,
                     "ioh_auc"     = AUC_inf,
                     "A"           = A,
                     "a"           = a,
                     "B"           = B,
                     "b"           = b,
                     "model_r2"    = model_r2, # this is a pseudo-r2
                     "ssr"         = ssr,
                     "sse"         = sse,
                     "ssl"         = ssl,
                     "k10"         = k10,
                     "k21"         = k21,
                     "k12"         = k12,
                     "n_early"     = length(dat_early$time),
                     "n_late"     = length(dat_late$time)    ) 
  } 
  if (tolower(nls_v) == "base") {
    fit = nls(iohexol  ~ A*exp(-a*time_min) + B*exp(-b*time_min) , 
              data=data.frame(time_min, iohexol),
              weights = wt,
              start=list(A = A_start,
                         B = B_start,
                         a = a_start,
                         b = b_start))  
    # Model Results
    A  = coef(fit)[["A"]]
    a  = coef(fit)[["a"]]
    B  = coef(fit)[["B"]]
    b  = coef(fit)[["b"]]
    AUC_inf <- A/a + B/b  # AUC_inf 
    mgfr_2c = iohexol_m/AUC_inf  # Dose/AUC_inf = mL/minutes (not indexed to BSA)
    mgfr_2c_bsa = ifelse(!is.na(bsa), mgfr_2c*1.73/bsa, NA)  # indexed to BSA
    iohexol_vd = iohexol_m/1000/(A+B) # volume of distribution, L
    # ODE micro parameters:
    k10 = iohexol_m/iohexol_vd/AUC_inf/1000 # 1/min
    k21 = a*b/k10
    k12 = a+b - k10 - k21
    # model fit parameters
    model_r2 = summary(lm(iohexol ~ predict(fit)))[["r.squared"]]   # this is a pseudo-r2
    ssr = sum(residuals(fit)^2)
    sse = sum(residuals(fit)[time_min<=t_early]^2)
    ssl = sum(residuals(fit)[time_min>=t_late]^2)
    mgfr_method =
      if (nls_weights & tolower(nls_v)=="gslnls") {"NLLS-gslnls-weighted"
      } else if (nls_weights & tolower(nls_v)=="base") {"NLLS-base-weighted"
      } else if (!nls_weights & tolower(nls_v)=="base") {"NLLS-base-unweighted"
      } else if (!nls_weights & tolower(nls_v)=="gslnls") {"NLLS-gslnls-unweighted"}
    res = data.frame("mgfr_method" = mgfr_method,
                     "mgfr_2c"     = mgfr_2c,
                     "mgfr_2c_bsa" = mgfr_2c_bsa,
                     "iohexol_m"   = iohexol_m,
                     "iohexol_0"   =  A+B,
                     "iohexol_vd"  = iohexol_vd,
                     "ioh_auc"     = AUC_inf,
                     "A"           = A,
                     "a"           = a,
                     "B"           = B,
                     "b"           = b,
                     "model_r2"    = model_r2, # this is a pseudo-r2
                     "ssr"         = ssr,
                     "sse"         = sse,
                     "ssl"         = ssl,
                     "k10"         = k10,
                     "k21"         = k21,
                     "k12"         = k12,
                     "n_early"     = length(dat_early$time),
                     "n_late"     = length(dat_late$time)    ) 
  } 
  if (output == "summary") {return(res)}  
  if (output == "gfr") return(mgfr_2c) # mGFR adjusted to 1.73m2 BSA
  if (output == "gfr_bsa") return(mgfr_2c_bsa) # mGFR adjusted to 1.73m2 BSA
  if (output == "fit"){ 
    if (nls_v == "SI") {return(list("Early"=lm_early, "Late"=lm_late))} else if (nls_v != "SI") {return(fit)}
  }
  if (output == "plot") {
    plot_nls_fit <- function(model, time, iohexol) {
      # Main plot:
      time_range = 0:max(time)
      plot(time, iohexol, pch=16, xlab = "Time after injection (minutes)", ylab="Iohexol (ug/mL)", 
           cex.lab=legend_cex, cex.axis=legend_cex, cex.main=legend_cex, cex.sub=legend_cex)
      if (nls_v != "SI") {lines(predict(model, list(time_min=0:max(time))), col="red", lty=2)
      } else if (nls_v == "SI") {
        lines(0:max(time), fit_SI_vals(0:max(time), A, a, B, b), col="red", lty=2)        }
      title(main = ifelse(!is.null(id),      # add subtitle with subject identifier
                          paste0("Study: ",id, "\nIohexol measured GFR, 2-Compartment model\nMethod: ", mgfr_method), 
                          paste0("Iohexol measured GFR, 2-Compartment model\nMethod: ", mgfr_method)), 
            adj = 0, cex.main=legend_cex)
      # Build Legend - model summary values:
      A  = A |> round(1)
      a  = a |> round(4)
      B  = B |> round(1)
      b  = b |> round(4)
      mgfr_2c = mgfr_2c |> round(1)
      mgfr_2c_bsa = mgfr_2c_bsa |> round(1)
      AUC_inf = AUC_inf |> round(1)
      model_r2 = model_r2 |> round(4)
      iohexol_vd = iohexol_vd |> round(2)
      legend("topright", adj=0.02, cex = legend_cex,
             legend=c(bquote(C[Ioh.]==.(A)*e^(-.(a)*t)+.(B)*e^(-.(b)*t)),
                      bquote(GFR==.(mgfr_2c)~mL/min),
                      bquote(GFR[bsa-adj]==.(mgfr_2c_bsa)~mL/min/1.73~m^2),
                      bquote(AUC[inf]==.(AUC_inf)~ug/mL*min),
                      bquote(Vd==.(iohexol_vd)~L),
                      bquote(pseudo-R^2==.(model_r2))) )
    }
    plot_nls_fit(fit, time_min, iohexol)}
  
}
JMLuther/tabletools documentation built on April 14, 2025, 3:09 a.m.