#' @title Fit negative binomial models to each taxa within an OTU table
#' @name bb_mods
#' @description Fit negative binomial models to each taxa within an OTU table through \code{\link[VGAM]{vglm}} in the \pkg{VGAM} package. Models can include a random effect if desired. Modesl will then be fit through \code{\link[lmer]{glmer.nb}} in the lmer package. Summaries for models or confidence intervals that fail to converge will not be returned, but taxa summaries will be provided in the output. Rank-Sum tests or presence/absence tests can be run on these taxa using \code{tidi_rank_sum} or \code{tidi_chisq}, respectively
#' @param micro_set A tidy_micro data set
#' @param table OTU table of interest
#' @param ... Covariates of interest. Can be interactions such as Group*Age
#' @param ref A character vector of the desired reference levels for each factor covariate. The order of the specifed references must match the order for the corresponding covariates specified in '...'
#' @param CI_method Character indicating the type of method used for confidence interval estimation. Wald intervals are the current default. Abbreviations allowed. See \code{\link[VGAM]{confintvglm}} for more details
#' @param SS_type Type of sums of squares calculated in \code{\link[VGAM]{anova.vglm}}. Either type II (2) or type III (3) sums of squares. Type II is the default
#' @param trace Print messages of model fitting proceedure
#' @references \code{\link[VGAM]{anova.vglm}}, \code{\link[VGAM]{vglm}}, \code{\link[VGAM]{betabinomial}}
#' @details Models containing only fixed effects are fit using \code{\link[VGAM]{vglm}} in the \pkg{VGAM} package. ANOVA / ANCOVA tests are conducted using a Likelihood Ratio test
#' @return A list containing several different model components and summaries
#' \item{Convergend_Summary}{A data.frame of model summaries from convergent models. Includes the Taxa name, the model coefficient, the estimated beta, the beta's 95 percent confidence interval, Z score, p_value, false discovery rate p-value}
#' \item{Estimate_Summary}{A data.frame of model estimates from convergent models intended to be ready for export for publications. Includes the Taxa name, the model coefficient, the estimated Rate Ratio, the Wald 95 percent confidence interval, the Z-score, and false discovery rate p-value}
#' \item{RA_Summary}{A data.frame of taxa summaries. Includes the Taxa name, grouping variables (each factor variable in your models), sample size (n), percent of 0 counts, basic summaries of relative abundance, percentiles of relative abundance, and a logical indicator of whether or not the model converged}
#' \item{formula}{The formula used in the model}
#' \item{Model_Coef}{Model coefficients (used in plotting funcitons)}
#' \item{Model_Covs}{Model covariates (used in plotting functions)}
#' @note False Discovery Rate p-values are calculated using \code{\link[stats]{p.adjust}}. Estimated rate ratios and confidence intervals for interactions in the Estimate_Summary table include all main effects. It is not simply the exponentiated interaction beta, it is the interaction of the sum of the intercept, corresponding main effect betas, and interaction betas
#' @examples
#' data(phy); data(cla); data(ord); data(fam); data(met)
#'
#' otu_tabs <- list(Phylum = phy, Class = cla, Order = ord, Family = fam)
#' set <- tidy_micro(otu_tabs = otu_tabs, meta = met)
#'
#' ## Fixed effects only
#' bb_fam <- set %>%
#' filter(day == 7) %>% ## Only including first week
#' mutate(bpd1 = factor(bpd1)) %>%
#'
#' ## Filtering out low abundance and unclassified taxa
#' ## These models will either break or we don't care about them
#' otu_filter(ra_cutoff = 0.1, exclude_taxa = c("Unclassified", "Bacteria")) %>%
#'
#' ## Negative binomial models for each Family of taxa with bpd1 as a covariate
#' bb_mods(table = "Family", bpd1)
#'
#' names(nb_fam)
#' bb_fam$Estimate_Summary
#' @export
bb_mods <- function(micro_set, table, ..., ref=NULL, CI_method = c("wald", "profile"),
SS_type = c("II", "III"), trace = FALSE){
if(table %nin% unique(micro_set$Table)) stop("Specified table is not in supplied micro_set")
micro_set %<>% dplyr::filter(Table == table) %>% ## Filtering out by table
dplyr::mutate(Taxa = factor(Taxa, levels = unique(Taxa)))
## Making Taxa levels the Taxa names w/in table for tapply
## Defining formula for glm.nb with w/ or w/o and Offset. Offset is the default
f <- formula_fun_bb(...)
## relevels the factor variables
if(!is.null(ref)){
if(!is.character(ref)) stop("ref must be a character string of valid reference levels")
Cov <- cov_str(...) %>%
dplyr::select(micro_set,.) ## pulling out model covariates
Fac <- Cov[sapply(Cov,class) == "factor"] ## selecting factor variables
## for loops are bad, but this should happen quickly (no need to apply)
## relevels each factor
for(i in 1:length(ref)){
Fac[i] <- relevel(Fac[,i], ref = ref[i])
}
Cov <- data.frame(Fac, Cov[sapply(Cov,class) != "factor"])
micro_set <- data.frame(
micro_set[which( !(names(micro_set) %in% names(Cov)) )],
Cov)
} else {
Cov <- cov_str(...) %>%
dplyr::select(micro_set,.) ## pulling out model covariates
}
if(missing(CI_method)) CI_method <- "wald"
if(grepl("[profile]", CI_method)) warning("Profile likelihood confidence intervals greatly increase computation time \n")
if(missing(SS_type)) SS_type <- 2
if(SS_type %nin% c(2,3,"II","III")) stop("SS_type must be either 2, 3, 'II', or 'III' \n")
Convergent_Models <- micro_set %>% ## Gives summaries of convergent models
plyr::ddply(~ Taxa, FE_bb, f. = f, trace = trace, method. = CI_method, SS_type. = SS_type, ...)
if(nrow(Convergent_Models) == 0) stop("No taxa models converged.\n")
## False Discovery Rate adjustment
Convergent_Models %<>%
dplyr::mutate(FDR_Pval = stats::p.adjust(P_val, method = "BH") %>% round(4))
## Gives Taxa summaries
RA_Summary <- micro_set %>% N.con(ra,!!!rlang::quos(...)) %>%
left_join(Convergent_Models %>%
dplyr::select(Taxa, FE_Converged), by = "Taxa") %>%
dplyr::arrange(FE_Converged, Taxa) %>% unique
## TRUE / FALSE for taxa converging
con_mod <- RA_Summary %>%
dplyr::ungroup() %>%
dplyr::distinct(Taxa, .keep_all = T) %>%
dplyr::pull(FE_Converged)
cat("\n", sum(con_mod), " taxa converged\n", sep = "")
cat(sum(!con_mod), "taxa did not converge\n")
result <- list(Convergent_Summary=Convergent_Models %>% ## Convergent model summaries
dplyr::filter(FE_Converged) %>%
dplyr::select(Taxa, Coef, Beta, CI, LRT, P_val, FDR_Pval),
Estimate_Summary=Convergent_Models %>%
dplyr::filter(FE_Converged) %>%
dplyr::select(Taxa, Coef, OR, CI_95, LRT, FDR_Pval) %>%
dplyr::filter(!grepl("(Intercept)", Coef)),
RA_Summary=RA_Summary, ## Nonconvergent summaries
formula=f, ## The formula used so you can check it is what you intended
Model_Coef = Convergent_Models %>%
dplyr::filter(FE_Converged) %>%
dplyr::select(Taxa, Coef, Intercept, Estimate = Beta, Cov_Type),
Model_Covs = Cov
)
result
}
## Function to fit VGAM:: beta binomial models and provide summary output
FE_bb <- function(set, f., trace, method., SS_type., ...){
# printing taxa to help keep track
cat("\nFitting", as.character(unique(set$Taxa)),"\n")
m <- try(VGAM::vglm(as.formula(f.), family = "betabinomial", trace = trace, data = set),
silent = T)
if(!inherits(m, "try-error")){
## Stops if confint doesn't work. Some models converge but confint breaks
CI <- try(VGAM::confintvglm(m, method = method.), silent = T)
lrt <- try(VGAM::lrt.stat.vlm(m, all.out = T, omit1s = F), silent = T)
if(!inherits(CI, "try-error") & !any(is.na(CI)) & !inherits(lrt, "try-error")){
s <- summary(m)
conv <- data.frame(
Taxa = as.character(unique(set$Taxa)), ## Taxa Names
Coef = names(m@coefficients), ## Coefficient Names
OR = bb_coef_est(m, ...) %>%
dplyr::pull(Est) %>% exp %>% round(4), ## Odds Ratios
CI_95 = bb_ci_est(m, ...), ## Wald intervals
LRT = lrt$Lrt.stat2 %>% round(4), ## LRT statistic
P_val = lrt$pvalues, ## P_value (not rounded for FDR calculation)
Intercept = m@coefficients[1], ## Intercepts for calculations
Beta = m@coefficients, ## Raw Coefs for later use
Cov_Type = cov_type(names(m@coefficients),
..., RE = NULL), ## Covariate types for Bar Charts (no random effects)
## CI for individual Beta's (profile likelihood)
CI = paste0("(", round(CI[,1],4), ", ", round(CI[,2],4), ")"),
FE_Converged = TRUE
) %>% Anova_SS_bb(m, SS_type.) ## Anova for each covariate
## Making empty rows when taxa models break
} else{
conv <- data.frame(
Taxa = as.character(unique(set$Taxa)),
Coef = character(1), OR = NA, CI_95 = character(1), LRT = NA, P_val = NA, Intercept = NA,
Beta = NA, Cov_Type = character(1), CI = character(1), FE_Converged = FALSE, Anova = NA)
}
} else {
conv <- data.frame(
Taxa = as.character(unique(set$Taxa)),
Coef = character(1), OR = NA, CI_95 = character(1), LRT = NA, P_val = NA, Intercept = NA,
Beta = NA, Cov_Type = character(1), CI = character(1), FE_Converged = FALSE, Anova = NA)
}
}
## Can change betabinomial to betabinomial(lrho = "rhobitlink") for rho close to 0.
## Might add extra step to fit using this model lrho = "rhobitlink" if original fails
## lrho = "rhobitlink" makes it an S4 class
## Names of linear predictors: logitlink(mu), logitlink(rho): The first intercept is the logit link of the mean
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.