R/ReasoningWAgg.R

Defines functions ReasoningWAgg

Documented in ReasoningWAgg

#' @title
#' Aggregation Method: ReasoningWAgg
#'
#' @description
#' Calculate one of several types of linear-weighted best estimates using supplementary
#' participant reasoning data to create weights.
#'
#' @details
#' Weighted by the breadth of reasoning provided to support the individuals’ estimate.
#'
#' `type` may be one of the following:
#'
#' \loadmathjax
#' **ReasonWAgg**: Weighted by the number of supporting reasons
#'
#' Giving greater weight to best estimates that are accompanied by a greater number of
#' supporting reasons may be beneficial. We will consider \mjeqn{w\_{reason}_{i,c}}{ascii}
#' to be the number of unique reasons provided by that individual \mjeqn{i}{ascii} in
#' support of their estimate for claim \mjeqn{c}{ascii}.
#'
#' \mjdeqn{\hat{p}_c(ReasonWAgg) = \sum_{i=1}^N \tilde{w}\_reason_{i,c}B_{i,c}}{ascii}
#'
#' See Hanea et al. (2021) for an example of reason coding.
#'
#' **ReasonWAgg2**: Incorporates both the number of reasons and their diversity across claims.
#'
#' The claim diversity component of this score is calculated per individual from all claims
#' they assessed. We assume each individual answers at least two claims. If an individual has
#' assessed only one claim, there weighting for that claim is equivalent to "ReasonWAgg".
#'
#' We will consider \mjeqn{w\_{varReason}_{i,c}}{ascii} to be the weighted "number of unique
#' reasons" provided by participant \mjeqn{i}{ascii} in support of their estimate for claim
#' \mjeqn{c}{ascii}. Assume there are \mjeqn{R}{ascii} total unique reasons any participant
#' can use to justify their numerical answers. Then, for each participant \mjeqn{i}{ascii} we
#' can construct a matrix \mjeqn{\mathbf{CR_i}}{ascii} with \mjeqn{R}{ascii} columns, each
#' corresponding to a unique reason, \mjeqn{r}{ascii}, and \mjeqn{C}{ascii} rows, where
#' \mjeqn{C}{ascii} is the number of claims assessed by that participant. Each element of
#' this matrix \mjeqn{\mathbf{CR_i}(r,c)}{ascii} can be either 1 or 0.
#' \mjeqn{\mathbf{CR_i}(r,c) = 1}{ascii} if reason \mjeqn{R_r}{ascii} was used to justify
#' the estimates assessed for \mjeqn{c}{ascii}, and \mjeqn{\mathbf{CR_i}(r,c) = 0}{ascii} if
#' reason \mjeqn{R_r}{ascii} was not mentioned when assessing claim \mjeqn{c}{ascii}. The
#' more frequently that a participant uses a given reason reduces the amount it contributes
#' to the weight assigned to that participant.
#'
#' \mjdeqn{w\_{varReason}_{i,c} =\sum_{r=1}^{R} \mathbf{CR_i}(c,r) \cdot (1 - \frac{\sum_{c=1}^C
#' \mathbf{CR_i}(c,r)}{C})}{ascii}
#'
#' \mjdeqn{\hat{p}_c(ReasonWAgg2) = \sum_{i=1}^N \tilde{w}\_varReason_{i,c}B_{i,c}}{ascii}
#' 
#' @note 
#' 
#' When `flag_loarmean` is set to `TRUE`, two additional columns will be returned; `method_applied` (a character variable describing the method actually applied with values of either `LoArMean` or `ReasonWAgg`) and `no_reason_score` (a logical variable describing whether no reasoning scores were supplied for any user for the given claim, where `TRUE` indicates no reasoning scores supplied and `FALSE` indicates that at least one participant for that claim had a reasoning score greater than 0).
#' 
#' named method_applied (with values LoArMean or ReasonWAgg), and no_reason_score, a logical variable describing whether or not there were no reasoning scores for that claim.
#'
#' @param expert_judgements A dataframe in the format of [data_ratings].
#' @param reasons A dataframe in the form of [data_supp_reasons]
#' @param type One of `"ReasonWAgg"`, `"ReasonWAgg2"`.
#' @param name Name for aggregation method. Defaults to `type` unless specified.
#' @param beta_transform Toggle switch to extremise confidence scores with the beta distribution. Defaults to `FALSE`.
#' @param beta_param Length two vector of alpha and beta parameters of the beta distribution. Defaults to `c(6,6)`.
#' @param placeholder Toggle the output of the aggregation method to impute placeholder data.
#' @param percent_toggle Change the values to probabilities. Default is `FALSE`.
#' @param flag_loarmean A toggle to impute LOArMean instead of ArMean when no participants have a reasoning weight for a specific claim (defaults `FALSE`).
#' @param round_2_filter Note that the IDEA protocol results in both a Round 1
#' and Round 2 set of probabilities for each claim. Unless otherwise specified,
#' we will assume that the final Round 2 responses (after discussion) are being
#' referred to.
#'
#' @return A tibble of confidence scores `cs` for each `paper_id`.
#'
#' @examples
#' \donttest{ReasoningWAgg(data_ratings)}
#'
#' @export
#' @md

ReasoningWAgg <- function(expert_judgements,
                          reasons = NULL,
                          type = "ReasonWAgg",
                          name = NULL,
                          beta_transform = FALSE,
                          beta_param = c(6, 6),
                          placeholder = FALSE,
                          percent_toggle = FALSE,
                          flag_loarmean = FALSE,
                          round_2_filter = TRUE) {

  if(!(type %in% c("ReasonWAgg",
                   "ReasonWAgg2"))){

    stop('`type` must be one of "ReasonWAgg" or "ReasonWAgg2"')

  }

  ## Set name argument

  name <- ifelse(is.null(name),
                 type,
                 name)

  cli::cli_h1(sprintf("ReasoningWAgg: %s",
                      name))

  if(isTRUE(placeholder)){

    method_placeholder(expert_judgements,
                       name)

  } else {

    df <- expert_judgements %>%
      preprocess_judgements(percent_toggle = {{percent_toggle}},
                            round_2_filter = {{round_2_filter}}) %>%
      dplyr::filter(element == "three_point_best") %>%
      dplyr::group_by(paper_id)

    switch(type,
           "ReasonWAgg" = {

             reason_weights <- weight_reason(reasons)

           },
           "ReasonWAgg2" = {

             reason_weights <- weight_reason2(reasons)

           })

    # work out which claims have no participants with reasoning scores

    with_rs <-
      reason_weights %>%
      dplyr::right_join(df, by = c("user_name", "paper_id"))

    # output summary data
    # identify claims that don't have quiz scores associated
    claim_summary_data <- with_rs %>%
      dplyr::group_by(paper_id) %>%
      dplyr::summarise(
        n_experts = dplyr::n(),
        no_reason_scores_for_claim = all(is.na(reason_count))
      )

    # loarmean for when there aren't quiz scores
    loarmean_data <- df %>%
      dplyr::full_join(claim_summary_data, by = "paper_id") %>%
      # filter to no quiz scores
      dplyr::filter(no_reason_scores_for_claim)

    loarmean_results <- if (nrow(loarmean_data) > 0) {
      loarmean_data %>%
        # Taking the core function of LOArMean
        dplyr::group_by(paper_id) %>%
        dplyr::mutate(value = dplyr::if_else(
          value == 1 | value == 0,
          value + .Machine$double.eps,
          value
        )) %>%
        dplyr::mutate(log_odds = log(abs(value / (1 - value)))) %>%
        dplyr::summarise(aggregated_judgement = mean(log_odds,
                                                     na.rm = TRUE),
                         n_experts = dplyr::n()
                         # first = min(timestamp,
                         #             na.rm = TRUE),
                         # last = max(timestamp,
                         #            na.rm = TRUE)
        ) %>%
        dplyr::mutate(aggregated_judgement =
                        exp(aggregated_judgement) /
                        (1 + exp(aggregated_judgement))) %>%
        dplyr::mutate(method = name)
    } else {NULL}

    # Process the no quiz scores dataframe to formatted output
    if(!is.null(loarmean_results)){
      loarmean_results <- postprocess_judgements(loarmean_results)
    }

    # calculate reason weight
    reasonwagg_data <-
      claim_summary_data %>%
      dplyr::select(paper_id, no_reason_scores_for_claim) %>%
      dplyr::full_join(with_rs) %>%
      # filter to paper_ids with quiz scores associated
      dplyr::filter(!no_reason_scores_for_claim)

    ReasonWAgg_results <- reasonwagg_data %>%
      # calculate scaled weight by claim
      dplyr::group_by(paper_id) %>%
      dplyr::summarise(agg_sum = sum(reason_count,
                                     na.rm = TRUE)) %>%
      dplyr::full_join(reasonwagg_data, by = "paper_id") %>%
      dplyr::mutate(agg_weight = reason_count / agg_sum) %>%
      dplyr::group_by(paper_id) %>%
      dplyr::summarise(
        aggregated_judgement = sum(agg_weight * value,
                                   na.rm = TRUE),
        n_experts = dplyr::n()) %>%
      dplyr::left_join(claim_summary_data) %>%
      # output
      dplyr::mutate(method = name) %>%
      postprocess_judgements()

    if(isTRUE(beta_transform)){

      ReasonWAgg_results <- ReasonWAgg_results %>%
        dplyr::mutate(cs = stats::pbeta(q = cs,
                                        shape1 = beta_param[1],
                                        shape2 = beta_param[2]))

    }

    output_data <-
      dplyr::bind_rows(loarmean_results, ReasonWAgg_results)

    if(flag_loarmean) {
      claim_summary_data %>%
        dplyr::select(paper_id, no_reason_scores_for_claim) %>%
        dplyr::full_join(output_data) %>%
        dplyr::mutate(method_applied =
                        dplyr::if_else(no_reason_scores_for_claim,
                                       "LOArMean", name))
    } else {

      output_data

    }
  }
}

Try the aggreCAT package in your browser

Any scripts or data that you put into this service are public.

aggreCAT documentation built on June 8, 2025, 11:06 a.m.