R/nma.R

Defines functions extractComparison nameTreatments makeTab calcAllPairs extractModelFit extractMTCResults extractTOI extractNodesplit saveModelCode extractRanks extractCoda saveDiagnostics plotEstimates plotRanks

Documented in calcAllPairs extractCoda extractComparison extractModelFit extractMTCResults extractNodesplit extractRanks extractTOI makeTab nameTreatments plotEstimates plotRanks saveDiagnostics saveModelCode

#' Extract treatment comparison information from mtc summary output
#'
#' @param df A data frame as returned by \code{calcAllPairs}
#'
#' @details In the summary output from a network meta-analysis treatment
#'   comparisons are commonly labelled as 'd.1.2' for the relative effect of
#'   treatment 2 compared to treatment 1. This function removes the 'd' and
#'   splits '1' and '2' into separate columns which is more useful for
#'   downstream reporting. This function is intended to work on the output from
#'   \code{calcAllPairs}. This functionality will usually be accessed via
#'   extractMTCResults and should only be used directly if you understand what
#'   you are doing.
#'
#' @return A data frame with additional columns
#'
#' @seealso \code{\link{calcAllPairs}}, \code{\link{extractMTCResults}}
#' @export
extractComparison = function(df) {
  #extract the information of which comparison is on each row from the
  #first column of the winBUGS output and store it in individual columns
  treatments = as.data.frame(stringr::str_extract_all(df$node, pattern = '\\d+',
                                             simplify = TRUE))
  treatments[,1] = as.integer(as.character(treatments[,1]))
  treatments[,2] = as.integer(as.character(treatments[,2]))
  colnames(treatments) = c('tA', 'tB')
  df = dplyr::tbl_df(dplyr::bind_cols(treatments, df[,2:ncol(df)]))
}

#' Match treatment names to ID numbers
#'
#' @param results A data frame as returned by \code{extractComparison}
#' @param coding A data frame with two columns 'id' and 'description'. 'id' must
#'   be the treatment id numbers corresponding to the way the treatments were
#'   coded in the network. 'description' should be the name of the treatment.
#'
#' @details This function matches the coded treatment id numbers in the network
#'   to the corresponding human readable names. The mapping from id number to
#'   name should be provided as a two column data frame via the \code{coding}
#'   argument. This function is intended to work on the data frame generated as
#'   the output from \code{extractComparison}. This function will mainly be
#'   called via \code{extractMTCResults} and should only be used directly if you
#'   understand what you are doing. The general work flow is \code{mtc.run} >
#'   \code{calcAllPairs} > \code{extractComparison} > \code{nameTreatments} >
#'   \code{makeTab}. \code{extractMTCResults} will handle the last four steps
#'   for you.
#'
#' @return The same data frame with the treatment names appended
#'
#' @seealso \code{\link{extractComparison}}, \code{\link{calcAllPairs}}, \code{\link{extractMTCResults}}
#' @export
nameTreatments = function(results, coding, ...) {

  #drop order column from coding. Makes no sense here
  coding = coding[,1:2]
  coding$id = as.integer(coding$id)

  #match intervention codes to their names
  results = dplyr::right_join(coding, results, by = c('id' = 'tB'))
  colnames(results)[1:2] = c('TreatmentB', 'nameB')

  #match comparator codes to their names
  results = dplyr::right_join(coding, results, by = c('id' = 'tA'))
  colnames(results)[1:2] = c('TreatmentA', 'nameA')
  return(results)
}

#' Generate a square grid reporting all pairwise comparisons of treatments in
#' the network
#'
#' @param results A data frame as returned by \code{nameTreatments}
#' @param coding  A data frame with two columns 'id' and 'description'. 'id'
#'   must be the treatment id numbers corresponding to the way the treatments
#'   were coded in the network. 'description' should be the name of the
#'   treatment. An optional third column named 'Order' may also be provided. If
#'   present this controls the order in which treatments are presented in the
#'   output. The values should be a sequence of numbers indicating the order in
#'   which the treatments should be sorted. If the 'Order' column is present
#'   then \code{reportOrder}  should be set to 'custom'
#' @param rounding The number of decimal places to be included in the output.
#'   The default is two.
#' @param reportOrder A character string indicating whether the treatments
#'   should be reported in the order the order they were provided (default) or
#'   if a custom order is required. Acceptable values are 'default' or 'custom'.
#'   If this is set to 'custom' then \code{coding} must contain a column named
#'   'Order'. See above.
#' @param ... Arguments passed from other functions, primarily
#'   \code{extractMTCResults}
#'
#' @details This function takes a data frame as returned by
#'   \code{nameTreatments} and returns a square grid containing all pairwise
#'   treatment comparisons in the network. The results are presented for column
#'   versus row. This is a common approach to reporting MTC results. This
#'   function should mainly be used via \code{extractMTCResults}
#'
#' @return A data frame
#'
#' @seealso \code{\link{extractMTCResults}}, \code{\link{nameTreatments}}
#' @export
makeTab = function(results, coding, rounding = 2, reportOrder = 'default', ...) {
  #create summary string
  results[,'effect'] = paste0(
    round(results$median, rounding), ' (',
    round(results$CrI_lower,rounding),' to ',
    round(results$CrI_upper,rounding), ')'
  )

  #The order of treatments in the table can be controlled manually
  #using the argument reportOrder.
  #The default is to order the table in the same order the treatments are coded
  #in the network
  if (reportOrder == 'custom') {
    coding = dplyr::arrange(coding, Order)
  } else {
    #order by treatment coding in the network
    coding$Order = coding$id
  }

  #Create a simple data frame to add output
  reportTab = data.frame('Treatment' = coding$id)

  for (i in 1:nrow(coding)) {
    comparator = coding$id[i]
    comp = dplyr::filter(results, TreatmentA == comparator)
    comp = dplyr::select(comp, TreatmentB, effect)
    colnames(comp)[ncol(comp)] = coding$id[i]
    reportTab = dplyr::full_join(reportTab, comp,
                          by = c('Treatment' = 'TreatmentB')
                          )
  }
  rownames(reportTab) = coding$description
  reportTab = dplyr::select(reportTab,-Treatment)
  colnames(reportTab) = coding$description

  reportTab = as.data.frame(t(reportTab))
}

#internal function used to access the S3 summary() method for mtc.result objects
.summariseMTC = getS3method('summary', 'mtc.result')

#' Calculate all pairwise treatment effects in a network meta-analysis
#'
#' @param mtcRes An object of class \code{mtc.results} as returned by mtc.run
#' @param expon Logical indicating whether the results should be exponentiated
#'   or not. If your analysis is based on log hazard ratio or log odds ratio etc
#'   then set this to TRUE to get the results as the corresponding hazard ratio
#'   or odds ratio.
#' @param ... Additional arguments passed down from extractMTCResults
#'
#' @details This function takes a \code{mtc.results} object and extracts summary
#'   statistics for all pairwise treatment comparisons in the network using
#'   \code{relative.effect} from gemtc. This function should mainly be used via
#'   \code{extractMTCresults} but can be called directly on any
#'   \code{mtc.results} object. Only results for the treatment comparisons are
#'   returned, additional nodes such as hetereogeneity parameters are not
#'   returned. Model comparisons statistics and heterogeneity parameters can be
#'   obtained using \code{extractModelFit}
#'
#' @return A data frame reporting summary statistics (mean, median etc) for the
#'   posterior distribution of each treatment comparison. Each row in the data
#'   frame represents one treatment comparison.
#'
#' @seealso \code{\link[gemtc]{mtc.run}}, \code{\link[gemtc]{relative.effect}}
#'   \code{\link{extractMTCResults}}, \code{\link{extractModelFit}}
#' @export
calcAllPairs = function(mtcRes, expon = FALSE, ...) {
  tid = as.integer(mtcRes$model$network$treatments$id)
  for (t in 1:length(tid)) {
    re = suppressWarnings(.summariseMTC(
      gemtc::relative.effect(mtcRes, t1 = tid[t], preserve.extra = FALSE)
    ))

    stats = re$summaries$statistics
    stats = data.frame(node = rownames(stats), stats, row.names = NULL)
    quan = re$summaries$quantiles
    quan = data.frame(node = rownames(quan), quan[,c(1,3,5)],
                      row.names = NULL)
    colnames(quan) = c('node','CrI_lower', 'median', 'CrI_upper')
    out = dplyr::full_join(stats, quan, by = 'node')

    if (t == 1) {
      output = out
    } else {
      output = suppressWarnings(dplyr::bind_rows(output, out))
    }
  }

  if (expon == TRUE) {
    output[,c(2,6:8)] = exp(output[,c(2,6:8)])
    output$SD = (output$CrI_upper - output$CrI_lower) / 3.92
  }
  output$Sample = nrow(mtcRes$samples[[1]])
  output = output[,c(1,7,6,8,2:5,9)]
}

#' Extract model comparison statistics from a \code{mtc.result} object
#'
#' @param mtcRes an object of class \code{mtc.result} as returned by
#'   \code{mtc.run} from gemtc
#'
#' @details This function returns model comparison statistics (DIC, pD, Dbar)
#'   from a \code{mtc.result} object. If the results object is derived from a
#'   random effects model then the between trials standard deviation will also
#'   be provided as sd.d. This function should mainly be used via
#'   \code{extractMTCResults} but can be used directly on a \code{mtc.result}
#'   object.
#'
#' @return A data frame containing the model comparison statistics.
#'
#' @seealso \code{\link{extractMTCResults}}, \code{\link[gemtc]{mtc.run}}
#' @export
extractModelFit = function(mtcRes) {
  modelSummary = .summariseMTC(mtcRes)
  dic = data.frame(
    'Mean' = modelSummary$DIC[1:3],
    'SD' = NA,
    row.names = c('Dbar', 'pD', 'DIC')
  )
  if (mtcRes$model$linearModel == 'random') {
    modelSD = modelSummary$summaries$statistics
    ix = rownames(modelSD) == 'sd.d'
    msd = data.frame(
      'Mean' = modelSD[ix,1],
      'SD' = modelSD[ix,2],
      row.names = rownames(modelSD)[ix]
    )
    dic = rbind(msd, dic)
  }
  return(dic)
}

#' Extract the results of a network meta-analysis from a \code{mtc.results} object
#'
#' @param res A \code{mtc.results} object as returned by \code{mtc.run}
#' @param resultsFile A character string indicating the path to the excel file
#'   where the results should be saved.
#' @param reference Name of the reference treatment for this analysis. This
#'   should match the name used in the input data file exactly
#' @param ... Additional arguments passed to underlying extractor functions,
#'   particularly calcAllPairs, nameTreatments and makeTab
#'
#' @details This function takes an object of class \code{mtc.result} and
#'   extracts the results for all pairwise treatment comparisons in the network.
#'   The results are returned as a data frame. This function is a wrapper that
#'   calls a series of other functions in the right order to do the actual work.
#'   \itemize{
#'    \item calcAllPairs calculates all pairwise treament comparisons
#'    in the network using \code{relative.effect} from the gemtc package
#'    \item extractComparison separates out the which pair of treatments
#'    are being compared
#'    \item nameTreatments matches the numbered treatment ID used by mtc.run to
#'    human readable treatment names
#'    \item makeTab produces a square grid of all pairwise treatment comparisons
#'    in the network. The results are presented for column versus row. This is a
#'    common approach to reporting MTC results.
#'    \item extractModelFit Extracts and returns model comparison statistics such
#'    as DIC, pD and Dbar
#'   }
#'
#'   The underlying functions can be used directly provided this order is
#'   maintained however this should not usually be necessary. If you take this
#'   approach you will also need to save all the respective outputs
#'   appropriately.
#'
#'   The \code{...} can be used to pass arguments to the underlying functions.
#'   The following arguments in particular will be required in almost all cases:
#'   \itemize{
#'   \item \code{expon} Logical indicating whether the results should be exponentiated
#'   or not. If your analysis is based on log hazard ratio or log odds ratio etc
#'   then set this to TRUE to get the results as the corresponding hazard ratio
#'   or odds ratio. Passed to \code{calcAllPairs}
#'   \item \code{coding} A data frame with two columns 'id' and 'description'. 'id'
#'   must be the treatment id numbers corresponding to the way the treatments
#'   were coded in the network. 'description' should be the name of the
#'   treatment. An optional third column named 'Order' may also be provided. If
#'   present this controls the order in which treatments are presented in the
#'   output. The values should be a sequence of numbers indicating the order in
#'   which the treatments should be sorted. If the 'Order' column is present
#'   then \code{reportOrder}  should be set to 'custom'. Passed to \code{makeTab}
#'   \item \code{reportOrder} A character string indicating whether the treatments
#'   should be reported in the order the order they were provided (default) or
#'   if a custom order is required. Acceptable values are 'default' or 'custom'.
#'   If this is set to 'custom' then \code{coding} must contain a column named
#'   'Order'. See above. Passed to \code{makeTab}
#'   }
#'
#' @seealso \code{\link[gemtc]{mtc.run}},
#'   \code{\link{calcAllPairs}}, \code{\link{extractComparison}},
#'   \code{\link{nameTreatments}}, \code{\link{makeTab}},
#'   \code{\link{extractModelFit}}
#'
#' @export
extractMTCResults = function(res, resultsFile, reference, ...) {
  #calculate all pairwise effects
  pairwiseResults = calcAllPairs(res, ...)

  #extract the comparison info
  #map treatment names to numbers
  pairwiseResults = extractComparison(pairwiseResults)
  pairwiseResults = nameTreatments(pairwiseResults, ...)
  XLConnect::writeWorksheetToFile(file = resultsFile, data = as.data.frame(pairwiseResults),
                                  sheet = 'AllComparisons', clearSheets = TRUE)

  #make a table of all pairwise comparisons and save it as an excel file
  reportTab = makeTab(results = pairwiseResults, ...)
  reportTab = dplyr::select(reportTab,-matches(reference)) #drop the reference column

  rt = as.data.frame(reportTab, check.names = FALSE) %>%
    tibble::rownames_to_column(var = 'Comparator')

  XLConnect::writeWorksheetToFile(file = resultsFile, data = as.data.frame(rt),
                                  sheet = 'Report', clearSheets = TRUE)

  #extract and save the model fit information
  modelFit = extractModelFit(res) %>%
    tibble::rownames_to_column(var = 'Parameter')
  XLConnect::writeWorksheetToFile(file = resultsFile, data = as.data.frame(modelFit),
                                  sheet = 'DIC', clearSheets = TRUE)

  return(pairwiseResults)
}

#' Extract all treatment comparisons involving a specific treatment if interest
#'
#' @param df A data frame as produced by \code{extractMTCResults}
#' @param toi An integer specifying the id number of the treatment of interest
#'   in the network
#' @param treatments A data frame with two columns 'id' and 'description'. 'id'
#'   must be the treatment id numbers corresponding to the way the treatments
#'   were coded in the network. 'description' should be the name of the
#'   treatment. An optional third column named 'Order' may also be provided. If
#'   present this controls the order in which treatments are presented in the
#'   output. The values should be a sequence of numbers indicating the order in
#'   which the treatments should be sorted. If the 'Order' column is present
#'   then set \code{orderResults=TRUE}
#' @param intervention Logical indicating whether the treatment of interest is
#'   an intervention or a comparator (e.g. placebo). This controls which column
#'   of \code{df} is searched to identify relevant treatment comparisons
#' @param reportOrder A character string indicating whether the treatments
#'   should be reported in the order the order they were provided (default) or
#'   if a custom order is required. Acceptable values are 'default' or
#'   'custom'. If this is set to 'custom' then \code{treatments} must contain a
#'   column named 'Order', see above.
#'
#' @details This is a simple filtering function to return all treatment
#'   comparisons involving a specific treatment of interest. This function is
#'   designed to be used with the output of \code{extractMTCResults}. There are
#'   two intended use cases:
#'   \enumerate{
#'   \item To return results for a given intervention of interest compared to
#'   all other treatments in the network
#'   \item To return results for all interventions compared to a given control
#'   treatment, e.g. placebo.
#'   }
#'
#' @return A data frame containing all treatment comparisons involving the
#'   specified treatment of interest.
#'
#' @seealso \code{\link{extractMTCResults}}
#' @export
extractTOI = function(df, treatments, toi, intervention = TRUE,
                      reportOrder = 'default') {

  #extract the treatment of interest
  if (intervention == TRUE) {
    df = dplyr::filter(df, TreatmentB == toi)
  } else {
    df = dplyr::filter(df, TreatmentA == toi)
  }

  #sort if required
  if (intervention == TRUE && reportOrder == 'custom') {
    df = dplyr::left_join(df, treatments[,2:3], by = c('nameA' = 'description'))
    df = dplyr::arrange(df, Order)
    df$Order = 1:nrow(df)
  }
  if (intervention == FALSE && reportOrder == 'custom') {
    df = dplyr::left_join(df, treatments[,2:3], by = c('nameB' = 'description'))
    df = dplyr::arrange(df, Order)
    df$Order = 1:nrow(df)
  }

  return(df)
}

#' Extract node splitting results
#'
#' @param ns.res An object of class \code{mtc.nodesplit}
#' @param treatments A data frame with columns 'description' defining the
#'   treatment names and 'id' defining the treatment ID numbers.
#' @param back_calc A logical indicating whether results should be back transformed.
#'   If set to TRUE then log odds ratios (or hazard ratios etc) will
#'   be converted to odds ratios (or hazard ratios etc).
#'
#' @details This function takes the output from the function mtc.nodesplit and
#'   returns a data frame of the results for each treatment comparison that can
#'   be split. The results are presented as one row per treatment comparison.
#'
#'
#' @return A data frame. Column headers starting 'dir.' are the treatment effect
#'   and corresponding 95\% confidence intervals for the direct comparison. Column
#'   headers starting 'ind.' report the same information for the indirect
#'   comparison. Column headers starting 'cons.' report the pooled estimate
#'   using both direct and indirect evidence under a standard consistency model.
#'   Columns labelled RoR report the ratio of direct:indirect effect estimates
#'   and the corresponding 95\% confidence interval
#'
#' @seealso \code{\link[gemtc]{mtc.nodesplit}}
#' @export
extractNodesplit = function(ns.res, treatments, backtransf) {
  #get summary and retrieve direct, indirect and consistency effect estimates
  ns.res = summary(ns.res)
  splitDir = ns.res$dir.effect
  colnames(splitDir)[3:5] = paste0('dir.', colnames(splitDir)[3:5])
  splitInd = ns.res$ind.effect
  colnames(splitInd)[3:5] = paste0('ind.', colnames(splitInd)[3:5])
  splitCons = ns.res$cons.effect
  colnames(splitCons)[3:5] = paste0('cons.', colnames(splitCons)[3:5])

  #put all the results together in a sensible table
  nsRes = dplyr::left_join(splitDir, splitInd, by = c("t1", "t2")) %>%
    dplyr::left_join(splitCons, by = c("t1", "t2")) %>%
    dplyr::left_join(ns.res$p.value, by = c("t1", "t2"))
  #calculate the inconsistency factor
  #difference of log odds ratios (or rate ratio, hazard ratio etc)
  #can also be difference of mean differences for continuous
  nsRes$IF = nsRes$dir.pe - nsRes$ind.pe
  #calculate variance of inconsistency factor
  #sum of Var(direct) + Var(indirect)
  varIF = ((nsRes$dir.ci.u - nsRes$dir.ci.l) / 3.92) ^ 2 +
    ((nsRes$ind.ci.u - nsRes$ind.ci.l) / 3.92) ^ 2
  #Std Error of inconsistency factor
  seIF = sqrt(varIF)
  #CI for inconsistency factor
  nsRes$IF.lower.ci = nsRes$IF - (1.96 * seIF)
  nsRes$IF.upper.ci = nsRes$IF + (1.96 * seIF)

  #rearrange column order
  nsRes = dplyr::select(nsRes, 1:11, 13:ncol(nsRes), 12)
  nsRes$t1 = as.numeric(nsRes$t1)
  nsRes$t2 = as.numeric(nsRes$t2)

  #convert log values to linear scale if required
  if(backtransf) {
    nsRes[,3:14] = exp(nsRes[,3:14])
  }
  nsRes[,3:15] = round(nsRes[,3:15], 3)
  #add treatment names
  nsRes = dplyr::left_join(nsRes, treatments, by = c('t1' = 'id')) %>%
    dplyr::left_join(treatments[,1:2], by = c('t2' = 'id'))

  colnames(nsRes)[16:17] = c('Comparator', 'Intervention')
  #Rearrange the column order and return the result
  dplyr::select(
    nsRes, 2:1, 17:16, dplyr::starts_with('dir'),
    dplyr::starts_with('ind'), dplyr::starts_with('cons'),
    dplyr::contains('IF'), p
  )
}

#' Extract JAGS model code from a \code{mtc.results} object
#'
#' @param mtcRes An object of class \code{mtc.result} as returned by mtc.run
#'   from the gemtc package
#' @param modelFile A character string specify the file path where the code will
#'   be saved.
#'
#' @details The \code{mtc.run} function automatically generates JAGS code to
#'  specify a network meta-analysis model. This function extracts the JAGS code
#'  from the \code{mtc.results} object and saves it to a file to have a record of what
#'  was done.
#'
#' @seealso \code{\link[gemtc]{mtc.run}}
#' @export
saveModelCode = function(mtcRes, modelFile) {
  #create some basic descriptive text from the model object
  des = paste0('#Description: ', mtcRes$model$network$description, '\n')
  cat(des, file = modelFile)
  modelType = paste0('#Model Type: ', mtcRes$model$linearModel, ' effects\n')
  cat(modelType, file = modelFile, append = TRUE)
  consistency = paste0('#Consistency Assumption: ', mtcRes$model$type, '\n')
  cat(consistency, file = modelFile, append = TRUE)
  like = paste0('#Likelihood: ', mtcRes$model$likelihood, '\n')
  cat(like, file = modelFile, append = TRUE)
  link = paste0('#Link: ', mtcRes$model$link, '\n')
  cat(link, file = modelFile, append = TRUE)
  nchains = paste0('#Number of chains: ', mtcRes$model$n.chain,'\n')
  cat(nchains, file = modelFile, append = TRUE)

  #lastly save the actual model code
  cat(mtcRes$model$code, file = modelFile, append = TRUE)
}

#'Extract ranking probabilities
#'
#' @param ranks An object of class \code{mtc.rank.probability} as returned by
#'   the \code{probability} function in the \code{gemtc} package
#' @param treatments A data frame with columns 'description' defining the
#'   treatment names and 'id' defining the treatment ID numbers.
#' @details This function takes the ranking probablilities returned by \code{rank.probability}, matches the treatment names to the id numbers and returns the results as a data frame
#'
#' @return A data frame
#'
#' @seealso \code{\link[gemtc]{rank.probability}}
#' @export
extractRanks = function(ranks, treatments) {
  treatments$id = as.integer(treatments$id)

  class(ranks) = 'matrix'
  ranks = as.data.frame(ranks)
  colnames(ranks) = paste0('Rank', 1:ncol(ranks))
  ranks$id = as.integer(rownames(ranks))

  ranks = dplyr::left_join(treatments[,1:2], ranks, by = 'id')
  ranks = dplyr::arrange(ranks, dplyr::desc(Rank1))
}

#' Extract the coda from an mtc.result object
#'
#' @param mtcResults An \code{mtc.result} object as returned by \code{mtc.run}
#'   from the gemtc package
#' @param summarise logical. If TRUE (default) and the mtc.result object
#'   includes more than one chain then the mean across all chains is calculated
#'   for each variable at each iteration of the chains
#'
#' @details This function takes the output from running an MTC model using
#'   mtc.run, extracts the coda from the \code{mtc.result} object and returns a
#'   data frame. If the summarise argument is TRUE and multiple chains were used
#'   for the MCMC then this function will return the mean across all chains for
#'   each variable in the model at each iteration of the chain. This is the
#'   default behaviour. If the summarise argument is FALSE then results for each
#'   individual chain are preserved.
#'
#' @return If summarise = TRUE:
#' \itemize{
#' \item A data frame with one column per variable plus one column indicating
#' the iteration of the chain. The values for each variable will be the mean
#' across all chains for each iteration of the chain
#' }
#' If summarise = FALSE:
#' \itemize{
#' \item A data frame with one column per variable plus one column indicating
#' the iteration of each chain and one column indicating the chain numbered 1,
#' 2, 3 etc. The values for each variable will be the value of the variable at
#' the indicated iteration of the indicated chain.
#' }
#'
#' @seealso \code{\link[gemtc]{mtc.run}}
#' @export
extractCoda = function(mtcResults, summarise=TRUE) {
  #starts with the output from mtc.run
  #extract coda from results object
  #coerce to a matrix and then to a data frame
  mtcCoda = coda::as.mcmc.list(mtcResults)
  mtcCoda = as.data.frame(as.matrix(mtcCoda, iters = TRUE, chains = TRUE))

  if(summarise & max(unique(mtcCoda$CHAIN)) >1) {
    #Get the mean across all three chains for each variable separately at each
    #iteration of the chains.
    #results are on log scale
    mtcCoda = dplyr::group_by(mtcCoda, ITER) %>%
      dplyr::summarise_each(funs(mean),-CHAIN)
  }
  return(mtcCoda)
}

#' Save convergence diagnostics
#' @param mtc An object of class \code{mtc.result} as returned by \code{mtc.run}
#'   in the \code{gemtc} package
#' @param directory A character string indicating the directory to save the
#'   results. By default a directory named ConvergenceDiagnostics will be
#'   created as a subfolder of the current directory
#' @details Create pdf files of some basic diagnostic plots to check the
#'   convergence of MCMC models. Plots are produced by the ggmcmc package for
#'   the trace of the MCMC chain(s), the posterior density of the model
#'   parameters and the autocorrelation within chains
#'
#' @seealso \code{\link[ggmcmc]{ggmcmc}}
#' @export
saveDiagnostics = function(mtc, directory='./ConvergenceDiagnostics') {

  #if there is no directory to save the plot files then create one
  if (!dir.exists(directory)) {
    dir.create(directory, recursive = TRUE)
  }
  diagData = ggmcmc::ggs(as.mcmc.list(mtc)) #convert data to ggplot friendly format
  f = file.path(directory, 'Traceplot.pdf') #set file name and make traceplots
  ggmcmc::ggmcmc(
    diagData, file = f, plot = 'traceplot', param_page = 3,
    simplify_traceplot = 0.25
  )
  #set file name and make density plots
  f = file.path(directory, 'Density.pdf')
  ggmcmc::ggmcmc(diagData, file = f, plot = 'density', param_page = 3)
  #set file name and make autocorrelation plots
  f = file.path(directory, 'Autocorrelation.pdf')
  ggmcmc::ggmcmc(diagData, file = f, plot = 'autocorrelation', param_page = 3)
  #set file name and make gelman plots
  #this uses the plot from the coda package. The version in ggmcmc is not
  #very useful
  message('Saving BGR')
  pdf(file=file.path(directory, 'BGR.pdf'), paper='a4')
  coda::gelman.plot(coda::as.mcmc.list(mtc), ask=FALSE, lty='solid')
  graphics.off()
}

#' Plot the results of a network meta-analysis
#'
#' @param df A data frame containing summary statistics for the posterior
#'   distribution of the parameters in a network meta-analysis.
#' @param yvar A character string specifying the name of the column in \code{df}
#'   that contains the variable to be used on the y-axis. Usually this will be
#'   the names of the treatments in the network.
#' @param xvar A character string specifying the name of the column in \code{df}
#'   that contains the variable to be used on the x-axis. Usually this will be
#'   the median of the posterior distribution of the treatment effect
#' @param lowLimit,hiLimit Character strings specifying the names of the
#'   columns in \code{df} that specify the lower and upper limits of the 95\%
#'   credible intervals. If you have followed the work flow in this package
#'   these will be called 'CrI_lower' and 'CrI_upper' respectively.
#' @param xlabel A character string to be used as a label on the x-axis. This is
#'   usually the type of effect measure, e.g. 'Odds ratio', 'Rate Ratio' etc.
#' @param noEffectLine An integer indicating where to draw the line of no
#'   effect. For ratio measures this is usually 1. For mean differences this is
#'   usually 0.
#' @param report_order A character string indicating whether the treatments
#'   should be reported in the order the order they were provided (default) or
#'   if a custom order is required. Acceptable values are 'default' or
#'   'custom'.
#'
#' @details This function should most commonly be used with the output from
#'   \code{extractTOI} to plot the results for a particular treatment of interest. This
#'   function uses ggplot to do the actual plotting and returns a ggplot object
#'   which may be further modified if required or just saved to a file.
#'
#' @return A ggplot object
#'
#' @seealso \code{\link{extractTOI}}, \code{\link[ggplot2]{geom_point}},
#'   \code{\link[ggplot2]{geom_errorbarh}}
#' @export
plotEstimates = function(df, yvar, xvar = 'median', lowLimit = 'CrI_lower',
                         hiLimit = 'CrI_upper', xlabel = 'Effect Estimate',
                         noEffectLine = 1) {
  df = as.data.frame(df)
  if (!'Order' %in% colnames(df)) {
    df = df[order(df[,yvar]),]
    df$Order = 1:nrow(df)
  }
  df$values = paste0(sprintf("%.2f", df[,xvar]), ' (', sprintf("%.2f",df[,lowLimit]), ' to ', sprintf("%.2f", df[,hiLimit]), ')')

  #build the basic plot
  p = ggplot2::ggplot(df) + ggplot2::geom_point(ggplot2::aes_string(x = xvar, y = 'Order'), size = 2)
  p = p + ggplot2::geom_errorbarh(ggplot2::aes_string(y = 'Order', xmax = hiLimit, xmin = lowLimit), height = 0.15)
  p = p + ggplot2::scale_y_reverse(breaks = df[,'Order'], labels = df[,yvar], expand = c(0, 0.2))


  #set sensible scale for x-axis
  #default is for ratio measures (OR, HR etc).
  #Mean difference needs special handling
  xrange = range(df[,lowLimit], df[,hiLimit])
  if (xlabel != 'Mean Difference' | xlabel != 'Effect Estimate') {
    xrange[1] = ifelse(xrange[1] > 0, 0, NA)
    xrange[2] = ifelse(xrange[2] < 2, 2, NA)
  } else {
    xrange = c(NA,NA)
  }
  p = p + ggplot2::scale_x_continuous(breaks = scales::pretty_breaks(n = 10), limits = xrange)
  p = p + ggplot2::labs(x = xlabel)
  p = p + ggplot2::geom_vline(xintercept = noEffectLine)
  p = p + ggplot2::theme_bw()

  #add annotations - values on right side
  p = p + ggplot2::theme(plot.margin = grid::unit(c(1,7,1,1), 'lines'))
  p = p + ggplot2::geom_text(ggplot2::aes_string(label = 'values',x = Inf, y = 'Order'),
                             hjust = -0.1, size = 3)

  #tidy up the final appearance
  p = p + ggplot2::theme(
    axis.title.y = ggplot2::element_blank(), panel.grid = ggplot2::element_blank(),
    axis.text = ggplot2::element_text(size = 8),
    axis.title = ggplot2::element_text(size = 8, vjust = 0),
    panel.border = ggplot2::element_rect(colour = 'black')
  )

  #Code to override clipping so that annotation is visible
  gt <- ggplot2::ggplot_gtable(ggplot2::ggplot_build(p))
  gt$layout$clip[gt$layout$name == "panel"] <- "off"
  return(gt)
  # grid::grid.draw(gt)
}

#' Plot rank probabilities
#'
#' @param ranks A data frame of ranking probabilities for each treatment as
#'   returned by \code{extractRanks}
#' @details This function takes a data frame of ranking probabilities for each
#'   treatment in a network and constructs a 'rankogram'; i.e. a bar chart
#'   showing the probability of being ranked first, second, third etc. for each
#'   treatment
#' @return A ggplot object which can then be saved using an appropriate graphics
#'   device, e.g. jpeg, png, pdf etc.
#' @seealso \code{\link{extractRanks}}, \code{\link[gemtc]{rank.probability}}
#' @export
plotRanks = function(ranks) {
  ranks = tidyr::gather(ranks[,2:ncol(ranks)], Rank, Probability, -description)
  p = ggplot2::ggplot(ranks) +
    ggplot2::geom_bar(
      ggplot2::aes(x = description, y = Probability, fill = Rank),
      position = 'dodge', stat = 'identity'
    )
  p = p + ggplot2::scale_fill_brewer(palette = 'Dark2')
  p = p + ggplot2::theme_bw()
  p = p + ggplot2::theme(
    panel.grid = ggplot2::element_blank(),
    axis.text = ggplot2::element_text(size = 12),
    axis.text.x = ggplot2::element_text(
      angle = 45, hjust = 1, vjust = 1
    ),
    axis.title = ggplot2::element_text(size = 12, vjust = 0),
    axis.title.x = ggplot2::element_blank(),
    panel.border = ggplot2::element_rect(colour = 'black')
  )
}
RichardBirnie/mautils documentation built on July 12, 2019, 8:56 p.m.