knitr::opts_chunk$set( collapse = TRUE, comment = "#>" )
knitr::opts_chunk$set(warning = FALSE, message = FALSE) library(tabletools) library(tidyverse)
This package has evolved to include functions useful for metabolic studies in humans, in particular to assess insulin sensitivity.
Convenience functions are provided to convert glucose, insulin and other units to the desired units.
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")
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")
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.
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()
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()
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)
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)
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
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.
The estimated average glucose can be calculated from A1C:
calculate_avg_glucose(5.5)
Higher values indicate lower insulin sensitivity.
calculate_homair(ogtt1$glucose[1], ogtt1$insulin[1])
Higher values indicate higher insulin sensitivity.
calculate_quicki(ogtt1$glucose[1], ogtt1$insulin[1])
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.
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 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)
The modified Stumvoll formula uses only the OGTT data.
calculate_isi_stumvoll(ogtt1$time, ogtt1$glucose, ogtt1$insulin) #
calculate_isi_cederholm(ogtt1$time, ogtt1$glucose, ogtt1$insulin, weight=70)
The sensitivity index described by Gutt requires BMI:
calculate_isi_gutt(ogtt1$time, ogtt1$glucose, ogtt1$insulin, bmi=30)
The sensitivity index described by Caumo requires weight (kg):
calculate_isi_caumo(ogtt1$time, ogtt1$glucose, ogtt1$insulin, weight = 100)
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.
bigtt_air_0_30_120
bigtt_air_0_60_120
bigtt_si_0_30_120
bigtt_si_0_60_120
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)
The Avignon index returns multiple values in a dataframe format:
isi_avignon_basal
an estimate of insulin sensivity at basal insulinisi_avignon_2h
an estimate of insulin sensivity at 2hr insulinisi_avignon_mean
a weight average of the basal and 2hr results.calculate_isi_avignon(ogtt1$time, ogtt1$glucose, ogtt1$insulin, weight = 100)
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 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)
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")
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
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
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.
not yet incorporated
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.