knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)
knitr::opts_chunk$set(warning = FALSE, message = FALSE)
library(tabletools)
library(tidyverse)

Introduction

This package has evolved to include functions useful for metabolic studies in humans, in particular to assess insulin sensitivity.

Unit conversion functions

Convenience functions are provided to convert glucose, insulin and other units to the desired units.

Glucose and Insulin

Most functions default to using glucose (mg/dL) and insulin (uU/mL) as the input units because these are most commonly reported in the US.

To convert glucose from mg/dL to mmol/L, use the convert_glucose_to_mM function, indicating the original units of the glucose value.

convert_glucose_to_mM(100, glucose_units =  "mg/dL")

To convert insulin from uU/mL to pmol/L, use the convert_insulin_to_pM function, indicating the original units of the insulin value.

convert_insulin_to_pM(10, insulin_units = "uU/mL")

Weight and Height

Similar functions are available to convert weight and height to the desired units.

convert_weight_to_kg(150, weight_units = "lbs")

Height from inches to meters:

convert_length_to_m(70, length_units = "in")

OGTT Data

Real OGTT data is provided in two different formats, wide and nested. The same data is contained in each file, but in different formats. With some data wrangling these two formats can be converted to each other, but are provided separately for convenience.

Wide format OGTT data

The wide format is the most common way to store OGTT data, with each timepoint in a separate column. This is the format that is most often used in clinical research, and to some extent this is required by some databases (e.g. the commonly used RedCAP database).

This package includes a sample dataset ogtt_wide with glucose and insulin data in wide format. Additional demographic data is also included.

Wide format Data example

data(ogtt_wide)
ogtt_wide |> head()

Transform from wide to long format

The ease of trasforming from wide to long format is one major advantage of R and tidyr functions such as pivot_longer().

Most often I favor using long format data, and will transform wide to long format as follows: (selection of just the OGTT values)

ogtt_long <- 
  ogtt_wide |> 
  select(id, starts_with("ogtt_")) |> 
  pivot_longer(cols = -id,
               names_to = c(".value", "time"),
               names_pattern = "ogtt_(.*)_([0-9]+)",
               values_to = ".value",
               names_transform = list(time = ~as.integer(.x))) |> # could be more complicated if not integers
  group_by(id) # wise to group by id at this point

ogtt_long |> head(10)

This can then be "nested"

ogtt_long |> 
  nest(.key = "ogtt_df") |> # already grouped by id
  head()

Nested format OGTT Data

The ogtt_nested data file includes the OGTT data "nested", or collected into a single data frame for each participant. The OGTT dataframes (ogtt_df variable) are stored in a list-column so that each row of the dataframe contains only one participant's complete OGTT data. Some familiarity of tidyverse data handling is helpful for using this data fomat.

Nested format Data example

data("ogtt_nested")
ogtt_nested |> select(1:3, ogtt_df) |>  head()

A single OGTT can be pulled out using selection of the approprirate row index.

ogtt_nested$ogtt_df[[1]]

All of the OGTT data can be unnested into a long format using the unnest function from dplyr. It is helpful to group the data by the participant ID before performing other calculations.

data("ogtt_nested")

ogtt_long <- 
  ogtt_nested |> 
  select(id, ogtt_df) |> 
  unnest(cols = ogtt_df) |> 
  group_by(id)
head(ogtt_long,10)

Data plot examples

theme_set(theme_jml())
pl_glucose <- 
  ogtt_long |> 
  ggplot(aes(time, glucose)) +
  geom_line(aes(group=id), alpha=0.3)
pl_insulin<- ogtt_long |> 
  ggplot(aes(time, insulin)) +
  geom_line(aes(group=id), alpha=0.3)
cowplot::plot_grid(pl_glucose, pl_insulin, nrow=1)

Summary plots

pl_summary_glucose <- 
  ogtt_long |> 
  mutate(glucose_tolerance.f = factor_glucose_tolerance(glucose[time==120])) |> 
  group_by(time, glucose_tolerance.f) |> 
  my_summary(glucose)  |> 
  ggplot(aes(time, glucose_mean, group=glucose_tolerance.f, color=glucose_tolerance.f)) +
  geom_point() + geom_line() + 
  geom_pointrange(aes(ymin=glucose_mean-glucose_sem, 
                      ymax=glucose_mean+glucose_sem)) +
  labs(y="Glucose (mg/dL)", color= "Glucose Tolerance:")

pl_summary_insulin <-
  ogtt_long |> 
  mutate(glucose_tolerance.f = factor_glucose_tolerance(glucose[time==120])) |> 
  group_by(time, glucose_tolerance.f) |> 
  my_summary(insulin)  |> 
  ggplot(aes(time, insulin_mean, group=glucose_tolerance.f, color=glucose_tolerance.f)) +
  geom_point() + geom_line() + 
  geom_pointrange(aes(ymin=insulin_mean-insulin_sem, 
                      ymax=insulin_mean+insulin_sem)) +
  labs(y="Insulin (uU/mL)", color= "Glucose Tolerance:")


cowplot::plot_grid(pl_summary_glucose , 
                   pl_summary_insulin ,
                   rel_widths = c(1,1),
                   nrow = 1) 

OGTT Insulin sensitivity calculations

Sample OGTT Data, to use for fasting and OGTT based calculations.

# handling data stored in a dataframe
ogtt1 <- data.frame(time=c(0, 30, 60, 90, 120),              # minutes
                    glucose=c(93, 129, 178, 164, 97),        # mg/dL
                    insulin=c(12.8, 30.7, 68.5, 74.1, 44.0)) # uU/mL

# example from Gutch et al 2015
ogtt2 <- data.frame(time=c(0, 30, 60, 90, 120),              # minutes
                    glucose=c(100, 160, 160, 160, 140),      # mg/dL
                    insulin=c(5, 10, 10, 10, 5))             # uU/mL

Fasting Glucose and insulin

The HOMA-IR and QUICKI indices are used to assess insulin sensitivity. There is an updated HOMA2 calculation that is preferable to the original HOMA-IR calculation, but it is based on a series of equations that are not accessible to incorporate into a function. There is an online and downloadable calculator for HOMA2 that is readily available here.

There are multiple calculations based on fasting measurements that are not incorporated here.

A1C estimated average glucose

The estimated average glucose can be calculated from A1C:

calculate_avg_glucose(5.5)

HOMA-IR:

Higher values indicate lower insulin sensitivity.

calculate_homair(ogtt1$glucose[1], ogtt1$insulin[1])

QUICKI:

Higher values indicate higher insulin sensitivity.

calculate_quicki(ogtt1$glucose[1], ogtt1$insulin[1])

OGTT based calculations

Most functions include an option to indicate the measurement units with standard default values for time (min.), glucose (mg/dL), insulin (uU/mL), and the function will convert units internally to the units for calculations as originally described by the creator. Note this indicates the OGTT input units, and does not change the output units.

Matsuda Index

The Matsuda index is the most often used OGTT-based estimate of insulin sensitivity. Calculations can be verified at an online calculator here.

calculate_isi_matsuda(ogtt1$time, ogtt1$glucose, ogtt1$insulin)

OGIS

OGIS calculation requires height and weight (for BSA calculation). The OGIS calculations can be verified using an online calculator here. Note there are very small differences (almost negligible) between the results obtained with the online calculator versus the downloadable excel file. The function here is the same as used by the excel version.

calculate_isi_ogis(ogtt1$time, ogtt1$glucose, ogtt1$insulin,  height=1.70, weight = 70)

Stumvoll Index

The modified Stumvoll formula uses only the OGTT data.

calculate_isi_stumvoll(ogtt1$time, ogtt1$glucose, ogtt1$insulin) #

Cederholm Index

calculate_isi_cederholm(ogtt1$time, ogtt1$glucose, ogtt1$insulin, weight=70)

Gutt ISI

The sensitivity index described by Gutt requires BMI:

calculate_isi_gutt(ogtt1$time, ogtt1$glucose, ogtt1$insulin, bmi=30)

Caumo Si

The sensitivity index described by Caumo requires weight (kg):

calculate_isi_caumo(ogtt1$time, ogtt1$glucose, ogtt1$insulin, weight = 100)

BIGTT

The BIGTT returns multiple values for acute insulin response (_air_) and insulin sensitivity (_si_) in a dataframe format, using values from either 0,30 or 0,60 and 120min.

The BIGTT calculations require gender and BMI, or will return NA as the result.

calculate_isi_bigtt(ogtt1$time, ogtt1$glucose, ogtt1$insulin, gender = "F", bmi=30)

Avignon ISI

The Avignon index returns multiple values in a dataframe format:

calculate_isi_avignon(ogtt1$time, ogtt1$glucose, ogtt1$insulin, weight = 100)

Belfiore ISI

The Belfiore result is normalized by reported normal values of insulin and glucose. This could be modified by adjusting the function but is not currently offered as an argument .

calculate_isi_belfiore(ogtt1$time, ogtt1$glucose, ogtt1$insulin)

Calculations on multiple OGTTs

Calculations are most easily performed on individual OGTT data, or multiple OGTT results stored in the nested data format.

Caveat: functions used here have not all been validated against known results- use with caution and verify results yourself

ogtt_isi <- 
  ogtt_nested |>  
  mutate(glucose_0 = map_dbl(ogtt_df, ~.x$glucose[.x$time==0]),
         insulin_0 = map_dbl(ogtt_df, ~.x$insulin[.x$time==0]),
         glucose_fasting.f = factor_glucose_fasting(map_dbl(ogtt_df, ~.x$glucose[.x$time==0])),
         glucose_tolerance.f = factor_glucose_tolerance(map_dbl(ogtt_df, ~.x$glucose[.x$time==0])),
         isi_homair = map_dbl(ogtt_df, ~calculate_homair(.x$glucose[.x$time==0], .x$insulin[.x$time==0])),
         isi_quicki = map_dbl(ogtt_df, ~calculate_quicki(.x$glucose[.x$time==0], .x$insulin[.x$time==0])), # Uses Log10 c/w Katz publication
         isi_avignon = map2_df(ogtt_df, weight_kg, ~calculate_isi_avignon(.x$time, .x$glucose, .x$insulin, weight=.y)), # no match
         isi_belfiore = map_dbl(ogtt_df, ~calculate_isi_belfiore(.x$time, .x$glucose, .x$insulin)), # no match
         isi_bigtt =pmap_df(list(ogtt_df, gender.f, bmi),                                           # Match within rounding error
                            ~calculate_isi_bigtt(..1$time, ..1$glucose, ..1$insulin, gender=..2, bmi=..3)),
         isi_caumo = map2_dbl(ogtt_df, weight_kg, ~calculate_isi_caumo(.x$time, .x$glucose, .x$insulin, weight=.y)), 
         isi_cederholm = map2_dbl(ogtt_df, weight_kg, ~calculate_isi_cederholm(.x$time, .x$glucose, .x$insulin, weight=.y)), # No match
         isi_gutt = map2_dbl(ogtt_df, bmi, ~calculate_isi_gutt(.x$time, .x$glucose, .x$insulin, bmi=.y)), # Match
         isi_stumvoll = map_dbl(ogtt_df, ~calculate_isi_stumvoll(.x$time, .x$glucose, .x$insulin)), # Match
         isi_stumvoll_dem = pmap_dbl(list(ogtt_df, bmi, age),                                       # Match
                                     ~calculate_isi_stumvoll_dem(..1$time, ..1$glucose, ..1$insulin, bmi=..2, age=..3)),
         isi_matsuda = map_dbl(ogtt_df, ~calculate_isi_matsuda(.x$time, .x$glucose, .x$insulin)),   # No match
         isi_ogis = pmap_dbl(list(ogtt_df, height_cm/100, weight_kg), 
                             ~calculate_isi_ogis(..1$time, ..1$glucose, ..1$insulin, height=..2, weight=..3)),
         beta = map_df(ogtt_df, ~calculate_stumvoll_beta(.x$time, .x$glucose, .x$insulin)),
         igi_30 = map_dbl(ogtt_df, ~calculate_igi30(.x$time, .x$glucose, .x$insulin)),

  )
ogtt_isi |> select(id, glucose_tolerance.f, glucose_fasting.f, glucose_0, insulin_0, starts_with("isi_"), beta, igi_30)

Summary Plots

ogtt_isi |> select(id, glucose_tolerance.f, glucose_fasting.f, starts_with("isi_"), beta) |> 
  unnest() |> 
  pivot_longer(cols = -c(id, glucose_tolerance.f, glucose_fasting.f),
               names_to = "var",
               values_to = "value") |> 
    # group_by(glucose_tolerance.f, var) |> 
  # my_summary(value)   
  ggplot(aes(glucose_tolerance.f, value)) +
  geom_boxplot(fill="steelblue", alpha=0.3) +
  scale_y_log10() +
  facet_wrap(~var, scales = "free_y")

Body Surface Area

The bsa command calculates Body Surface area (m2) using the most commonly available methods Yu et al.

The default method is the Mosteller formula. Functions will conduct unit conversions if the proper units are provided- defaults to kg and m.

calculate_bsa(weight = 70, height =  1.50, method = "Mosteller",
              weight_units = "kg", height_units = "m")

Available Methods and Formulas:

```{=tex} \begin{equation} \sqrt{\frac{Ht(cm) \cdot Wt(kg)}{3600}} \tag{Mosteller} \end{equation}

```{=tex}
\begin{equation}
Wt(kg)^{0.425} \cdot Ht(cm)^{0.725} \cdot 0.007184 \tag{DuBois and DuBois}
\end{equation}

```{=tex} \begin{equation} Wt(kg)^{0.51456} \cdot Ht(cm)^{0.42246} \cdot 0.0235 \tag{Gehan and George} \end{equation}

```{=tex}
\begin{equation}
Wt(kg)^{0.5378} \cdot Ht(cm)^{0.3964} \cdot 0.024265 \tag{Haycock}
\end{equation}

```{=tex} \begin{equation} 71.3989 \cdot Ht(cm)^{0.7437} \cdot Wt(kg)^{0.4040} \div 10000 \tag{Yu} \end{equation}

```{=tex}
\begin{equation}
0.1173 \cdot Wt(kg)^{0.6466} \tag{Livingston}
\end{equation}

```{=tex} \begin{equation} 128.1 \cdot Ht(cm)^{0.60} \cdot Wt(kg)^{0.44} \tag{Tikuisis} \end{equation}

# Kidney function equations

## eGFR

Creatinine clearance is the historical standard to estimate glomerular filtration rate (GFR). The Cockcroft-Gault formula was used in the past, but has been superceded by the MDRD and more recently CKD-EPI equations. Newer methods incorporating Cystatin C are also available, and may increase accuracy when using race-free equations [@Inker2021].

Formulas for multiple methods of eGFR are provided- not meant to be comprehensive, but up to date. The ASN and NKF task force panels now recommend using equations that do not incorporate a race coefficient. Details on the formulas can be found in Supplemental Table S10 of [@Inker2021].

Online calculators to verify the results are available:

-   New CKD-EPI equations at [Kidney.org](https://www.kidney.org/professionals/kdoqi/gfr_calculator)\
-   Old 2009 CKD-EPI (race-based) calculator is maintained at [NIH/NIDDK](https://www.niddk.nih.gov/health-information/professionals/clinical-tools-patient-management/kidney-disease/laboratory-evaluation/glomerular-filtration-rate-calculators/historical) for comparison.
-   MDRD

### Cockcroft-Gault

This formula estimates Creatinine Clearance, whereas others estimate GFR.
The Cockcroft-Gault Creatinine Clearance estimating equation was used for decades in clinical practice before the MDRD and CKD-EPI equations were developed. 

$$Creatinine~CL_{CG}= \frac{(140-Age) \cdot Weight \cdot 0.85[Female]}{72 \cdot Creatinine}$$

where units are Weight (kg), Age (years), and Creatinine (mg/dL).


```r
calculate_creatclearance_cg(age=70, sex="Male", weight=70, creatinine=1.0) # 68

CKD-EPI

Note: When comparing results using this function against Inker Table S11 there are minor differences amounting to no more than 0.5-1.0 ml/min/1.73m2.

The 2021 CKD-EPI equation is: $$eGFR_{cr}= 142\cdot min(Cr/\kappa, 1)^\alpha \cdot max(Cr/\kappa, 1)^{-1.200} \cdot 0.9938^{Age} \cdot 1.102[Female] $$

where:

* $\kappa$ = 0.7 (Female) or 0.9 (Male)\ * $\alpha$ = -0.241 (Female) or -0.302 (Male)\ * Age is in years * Cr = serum Creatinine in mg/dL

Note that the 2021 CKD-EPI formula also has a version which was derived using Race, and produces slightly different results.

$$eGFR_{cr}= 141\cdot min(Cr/\kappa, 1)^\alpha \cdot max(Cr/\kappa, 1)^{-1.209} \cdot 0.9929^{Age} \cdot 1.018[Female] \cdot 1.159[Black] $$

where:

* $\kappa$ = 0.7 (Female) or 0.9 (Male)\ * $\alpha$ = -0.329 (Female) or -0.411 (Male)\

calculate_egfr_ckdepi(age=70, sex="Male", creatinine=0.8) # 95.2

MDRD

The MDRD equation was the first GFR estimating equation incorporated into widespread clinical practice.[@Levey2006] It has fallen out of use in favor of the newer CKD-EPI equations. Limitations include use of racial adjustments that are biased, and overestimation of GFR especially at higher values.

The best accepted MDRD version is the 4-variable equation incorporating age, sex, race, and creatinine.

$$eGFR_{MDRD}= 175 \cdot Cr^{-1.154} \cdot Age^{-0.203} \cdot 1.212[Black] \cdot 0.742[Female]$$ Where the race and sex adjustment is included if Black or Female.

CKD-EPI Cr-Cystatin-C

not yet incorporated

Bibliography



JMLuther/tabletools documentation built on April 14, 2025, 3:09 a.m.