R/legacy.R

Defines functions rbind.bucss_plan `names<-.bucss_plan` `$<-.bucss_plan` `[[<-.bucss_plan` `[<-.bucss_plan` `[.bucss_plan` `[[.bucss_plan` ss.power.reg.joint ss.power.reg.all ss.power.reg1 ss.power.spa.general ss.power.spa ss.power.wa.general ss.power.wa ss.power.ba.general ss.power.ba ss.power.dt ss.power.it .translate_effect .deprecate_once

Documented in rbind.bucss_plan ss.power.ba ss.power.ba.general ss.power.dt ss.power.it ss.power.reg1 ss.power.reg.all ss.power.reg.joint ss.power.spa ss.power.spa.general ss.power.wa ss.power.wa.general

# Deprecated 1.x API, retained for backward compatibility. Each dot-named
# function from BUCSS 1.x is kept here as a thin wrapper that translates its
# arguments to the 2.x convention and calls the corresponding ss_buc_* planner,
# returning that planner's bucss_plan object. The wrappers are not actively
# developed; the statistical work lives in the ss_buc_* functions. A one-time
# (per session, per function) deprecation warning steers users to the new name;
# the ss_buc_* functions themselves never issue it. The 1.x grid-resolution
# 'step' argument is accepted for call compatibility and ignored (the 2.0.0
# engine finds the noncentrality parameter by root finding, not a grid). The
# 1.x effect values (e.g. "factor.A", "between.only") are translated to their
# snake_case 2.x forms with a single dot-to-underscore rule.

# Track which deprecated functions have already warned this session, so the
# warning fires once per function per session rather than on every call.
.bucss_deprecated_warned <- new.env(parent = emptyenv())

.deprecate_once <- function(old, replacement) {
  if (is.null(.bucss_deprecated_warned[[old]])) {
    assign(old, TRUE, envir = .bucss_deprecated_warned)
    msg <- paste0(
      "'", old, "' is deprecated as of BUCSS 2.0.0 and is no longer actively ",
      "developed. It now calls ", replacement, " and returns a 'bucss_plan' ",
      "object (its first two elements remain the necessary sample size and the ",
      "adjusted noncentrality parameter, so 'result[[1]]' and 'result[[2]]' ",
      "still work). Please use the new function directly. This warning is shown ",
      "once per session."
    )
    # A classed condition (mirroring R's own .Deprecated(), which signals a
    # 'deprecatedWarning') so callers can catch or suppress it by class; call =
    # NULL keeps the uninformative wrapper call out of the message.
    warning(warningCondition(
      msg, class = c("bucss_deprecatedWarning", "deprecatedWarning"),
      call = NULL))
  }
  invisible(NULL)
}

# Translate a 1.x dotted 'effect' value to its 2.x snake_case form. The single
# dot-to-underscore rule covers every case: "factor.A" -> "factor_A",
# "between.only" -> "between_only", and the dotless values ("between",
# "interaction") are unchanged.
.translate_effect <- function(effect, choices_1x) {
  gsub(".", "_", .check_effect(effect, choices_1x), fixed = TRUE)
}

#' Deprecated 1.x Functions in BUCSS
#'
#' @description The dot-named functions from BUCSS 1.x are \strong{deprecated} as
#'   of BUCSS 2.0.0 and are no longer actively developed. They are retained so
#'   that scripts written for BUCSS 1.x continue to run. Each one
#'   translates its arguments to the 2.x convention and calls the corresponding
#'   \code{ss_buc_*} function, returning that function's
#'   \code{\link{bucss_plan}} object.
#'
#'   For backward compatibility the returned object still supports the 1.x
#'   positional extraction: \code{result[[1]]} is the necessary sample size and
#'   \code{result[[2]]} is the bias and uncertainty adjusted noncentrality
#'   parameter, the same two values the 1.x functions returned as an unnamed
#'   list (this is provided by a \code{[[} method for \code{bucss_plan}). Only
#'   those two subscripts are kept: code that treats the 1.x result as a list
#'   in other ways (\code{unlist(result)}, \code{result[[1L]]}, or printing it
#'   through \code{rbind()}) now sees the data frame. The richer named view
#'   (\code{result$value}, \code{tidy(result)}, \code{glance(result)}) is also
#'   available.
#'
#'   Calling one of these functions issues a deprecation warning that names its
#'   replacement, once per function per session; the \code{ss_buc_*} functions
#'   themselves never issue this deprecation warning. New code should call the
#'   \code{ss_buc_*} functions directly.
#'
#' @details The replacements are:
#'
#' \tabular{ll}{
#'   \strong{Deprecated} \tab \strong{Replacement} \cr
#'   \code{ss.power.it} \tab \code{\link{ss_buc_independent_t}} (alias \code{\link{ss_buc_smd}}) \cr
#'   \code{ss.power.dt} \tab \code{\link{ss_buc_paired_t}} (alias \code{\link{ss_buc_smd_paired}}) \cr
#'   \code{ss.power.ba} \tab \code{\link{ss_buc_one_way_anova}} (when \code{levels.B} is \code{NULL}) or \code{\link{ss_buc_factorial_anova}} \cr
#'   \code{ss.power.ba.general} \tab \code{\link{ss_buc_factorial_anova_general}} \cr
#'   \code{ss.power.wa} \tab \code{\link{ss_buc_rm_anova}} \cr
#'   \code{ss.power.wa.general} \tab \code{\link{ss_buc_rm_anova_general}} \cr
#'   \code{ss.power.spa} \tab \code{\link{ss_buc_mixed_anova}} \cr
#'   \code{ss.power.spa.general} \tab \code{\link{ss_buc_mixed_anova_general}} \cr
#'   \code{ss.power.reg1} \tab \code{\link{ss_buc_reg_coef}} \cr
#'   \code{ss.power.reg.all} \tab \code{\link{ss_buc_R2}} \cr
#'   \code{ss.power.reg.joint} \tab \code{\link{ss_buc_reg_joint}} \cr
#' }
#'
#'   Because BUCSS 2.0.0 finds the noncentrality parameter by direct root
#'   finding rather than the fixed 0 to 100 grid used in 1.x, the values these
#'   wrappers return can differ slightly from 1.x and are no longer capped when
#'   the implied parameter exceeds 100. The \code{step} argument is accepted for
#'   call compatibility and ignored.
#'
#'   Some 1.x behaviors deliberately changed, so a 1.x call can now error where
#'   it used to return a number. \code{ss.power.ba.general} rejects a
#'   \code{df.denominator} greater than \code{N - cells} (1.x accepted such a
#'   value and ignored the argument entirely, including in its own documented
#'   example). As in 1.x, an \code{assurance} or \code{power} of 1 or more is
#'   read as a percentage, so exactly 1 means 1 percent; after that reading, an
#'   \code{assurance} or \code{power} of 0, or of 100 (which means 1), is
#'   rejected rather than returning an artifact of the internal search. A
#'   \code{power} at or below \code{alpha.planned} after that reading is not
#'   allowable and stops too, so \code{power = 1} (which means .01) stops
#'   unless \code{alpha.planned} is below .01; 1.x returned the smallest
#'   sample size it considers for such a target, with no notice. An
#'   \code{alpha.prior} of 1 is computed as exactly 1, where 1.x computed
#'   with .999 in its place: most such plans are unchanged, and the rest are
#'   slightly smaller.
#'   \code{ss.power.ba} and \code{ss.power.wa} refuse \code{effect =
#'   "factor.B"} or \code{"interaction"} when \code{levels.B} is not given (1.x
#'   refused only the interaction, and silently returned the Factor A plan for
#'   \code{"factor.B"}). \code{ss.power.ba.general} and
#'   \code{ss.power.spa.general} reject numerator degrees of freedom no design
#'   of their kind can have (\code{df.numerator} above \code{cells - 1}; for
#'   the split plot, above \code{num.groups - 1} for the between-subjects
#'   effect, or not a multiple of \code{df.num.within} within that bound for
#'   the interaction). Non-scalar, non-finite, and fractional inputs are
#'   rejected with a message naming the argument. Most errors raised through
#'   these wrappers come from the \code{ss_buc_*} planners and name their
#'   arguments (\code{t_observed} for \code{t.observed}, and so on); an
#'   unrecognized \code{effect} is rejected by the wrapper itself. The
#'   regression wrappers keep the 1.x fixed-predictor computation
#'   (\code{random_predictors = FALSE}); call \code{\link{ss_buc_reg_coef}},
#'   \code{\link{ss_buc_R2}}, or \code{\link{ss_buc_reg_joint}} directly for
#'   predictors sampled along with the outcome. See \code{NEWS} for the full
#'   accounting.
#'
#' @param t.observed,F.observed The observed \emph{t} or \emph{F} statistic from
#'   the prior study (the 1.x spelling of \code{t_observed} / \code{F_observed}).
#' @param N Total sample size of the prior study (number of pairs for
#'   \code{ss.power.dt}).
#' @param n Per-group sample size(s) of the prior study, for \code{ss.power.it}.
#' @param p,p.joint Number of predictors, and the number tested jointly, for the
#'   regression planners.
#' @param levels.A,levels.B Number of levels of the first and (optional) second
#'   factor, for \code{ss.power.ba} and \code{ss.power.wa}.
#' @param levels.between,levels.within Number of levels of the between- and
#'   within-subjects factors, for \code{ss.power.spa}.
#' @param cells Number of cells (groups) in the between-subjects design, for
#'   \code{ss.power.ba.general}.
#' @param df.numerator,df.denominator Numerator and denominator degrees of
#'   freedom for the effect of interest in the prior study, for the
#'   \code{.general} planners.
#' @param df.num.within Numerator degrees of freedom of the within-subjects
#'   component, for \code{ss.power.spa.general} with
#'   \code{effect = "between.within"}.
#' @param num.groups Number of between-subjects groups, for
#'   \code{ss.power.spa.general}.
#' @param effect The effect to plan for, in the 1.x dotted spelling (translated
#'   internally to the 2.x form).
#' @param alpha.prior,alpha.planned The prior-study and planned-study Type I
#'   error rates (the 1.x spelling of \code{alpha_prior} / \code{alpha_planned}).
#'   \code{alpha.prior} also accepts \code{"No Publication Filter"}, which
#'   means 1.
#' @param assurance The desired assurance.
#' @param power The desired statistical power of the planned study, which
#'   must exceed \code{alpha.planned}.
#' @param step Ignored. Accepted only so that 1.x calls that set it do not error.
#'
#' @return A \code{\link{bucss_plan}} object, as returned by the corresponding
#'   \code{ss_buc_*} function.
#'
#' @seealso The \code{ss_buc_*} replacements listed above.
#'
#' @name bucss-deprecated
#' @keywords internal
NULL

#' @rdname bucss-deprecated
#' @export
ss.power.it <- function(t.observed, n, N, alpha.prior = .05, alpha.planned = .05,
                        assurance = .80, power = .80, step = .001) {
  .deprecate_once("ss.power.it", "'ss_buc_independent_t'")
  ss_buc_independent_t(t_observed = t.observed, n = n, N = N,
                       alpha_prior = alpha.prior, alpha_planned = alpha.planned,
                       assurance = assurance, desired_power = power)
}

#' @rdname bucss-deprecated
#' @export
ss.power.dt <- function(t.observed, N, alpha.prior = .05, alpha.planned = .05,
                        assurance = .80, power = .80, step = .001) {
  .deprecate_once("ss.power.dt", "'ss_buc_paired_t'")
  ss_buc_paired_t(t_observed = t.observed, N = N,
                  alpha_prior = alpha.prior, alpha_planned = alpha.planned,
                  assurance = assurance, desired_power = power)
}

#' @rdname bucss-deprecated
#' @export
ss.power.ba <- function(F.observed, N, levels.A, levels.B = NULL,
                        effect = c("factor.A", "factor.B", "interaction"),
                        alpha.prior = .05, alpha.planned = .05,
                        assurance = .80, power = .80, step = .001) {
  .deprecate_once("ss.power.ba",
                  "'ss_buc_one_way_anova' (one factor) or 'ss_buc_factorial_anova' (two factors)")
  # Without the second factor only Factor A can be planned. 1.x refused the
  # interaction here but silently returned the Factor A plan for "factor.B";
  # 2.0.0 refuses both, since dispatching to the one-way planner would hand a
  # user who asked for Factor B the plan for Factor A.
  choices_1x <- c("factor.A", "factor.B", "interaction")
  effect <- .check_effect(effect, choices_1x)
  if (is.null(levels.B) && !identical(effect, "factor.A")) {
    stop("You cannot select 'effect = \"", effect,
         "\"' if you do not specify 'levels.B'.", call. = FALSE)
  }
  if (is.null(levels.B)) {
    ss_buc_one_way_anova(F_observed = F.observed, N = N, levels_A = levels.A,
                         alpha_prior = alpha.prior, alpha_planned = alpha.planned,
                         assurance = assurance, desired_power = power)
  } else {
    effect <- .translate_effect(effect, choices_1x)
    ss_buc_factorial_anova(F_observed = F.observed, N = N, levels_A = levels.A,
                           levels_B = levels.B, effect = effect,
                           alpha_prior = alpha.prior, alpha_planned = alpha.planned,
                           assurance = assurance, desired_power = power)
  }
}

#' @rdname bucss-deprecated
#' @export
ss.power.ba.general <- function(F.observed, N, cells, df.numerator, df.denominator,
                                alpha.prior = .05, alpha.planned = .05,
                                assurance = .80, power = .80, step = .001) {
  .deprecate_once("ss.power.ba.general", "'ss_buc_factorial_anova_general'")
  ss_buc_factorial_anova_general(F_observed = F.observed, N = N, cells = cells,
                                 df_numerator = df.numerator,
                                 df_denominator = df.denominator,
                                 alpha_prior = alpha.prior,
                                 alpha_planned = alpha.planned,
                                 assurance = assurance, desired_power = power)
}

#' @rdname bucss-deprecated
#' @export
ss.power.wa <- function(F.observed, N, levels.A, levels.B = NULL,
                        effect = c("factor.A", "factor.B", "interaction"),
                        alpha.prior = .05, alpha.planned = .05,
                        assurance = .80, power = .80, step = .001) {
  .deprecate_once("ss.power.wa", "'ss_buc_rm_anova'")
  effect <- .translate_effect(effect, c("factor.A", "factor.B", "interaction"))
  ss_buc_rm_anova(F_observed = F.observed, N = N, levels_A = levels.A,
                  levels_B = levels.B, effect = effect,
                  alpha_prior = alpha.prior, alpha_planned = alpha.planned,
                  assurance = assurance, desired_power = power)
}

#' @rdname bucss-deprecated
#' @export
ss.power.wa.general <- function(F.observed, N, df.numerator,
                                alpha.prior = .05, alpha.planned = .05,
                                assurance = .80, power = .80, step = .001) {
  .deprecate_once("ss.power.wa.general", "'ss_buc_rm_anova_general'")
  ss_buc_rm_anova_general(F_observed = F.observed, N = N,
                          df_numerator = df.numerator,
                          alpha_prior = alpha.prior, alpha_planned = alpha.planned,
                          assurance = assurance, desired_power = power)
}

#' @rdname bucss-deprecated
#' @export
ss.power.spa <- function(F.observed, N, levels.between, levels.within,
                         effect = c("between", "within", "interaction"),
                         alpha.prior = .05, alpha.planned = .05,
                         assurance = .80, power = .80, step = .001) {
  .deprecate_once("ss.power.spa", "'ss_buc_mixed_anova'")
  effect <- .translate_effect(effect, c("between", "within", "interaction"))
  ss_buc_mixed_anova(F_observed = F.observed, N = N,
                     levels_between = levels.between, levels_within = levels.within,
                     effect = effect, alpha_prior = alpha.prior,
                     alpha_planned = alpha.planned, assurance = assurance,
                     desired_power = power)
}

#' @rdname bucss-deprecated
#' @export
ss.power.spa.general <- function(F.observed, N, df.numerator, num.groups,
                                 effect = c("between.only", "within.only", "between.within"),
                                 df.num.within, alpha.prior = .05,
                                 alpha.planned = .05, assurance = .80,
                                 power = .80, step = .001) {
  .deprecate_once("ss.power.spa.general", "'ss_buc_mixed_anova_general'")
  effect <- .translate_effect(effect,
                              c("between.only", "within.only", "between.within"))
  args <- list(F_observed = F.observed, N = N, df_numerator = df.numerator,
               num_groups = num.groups, effect = effect,
               alpha_prior = alpha.prior, alpha_planned = alpha.planned,
               assurance = assurance, desired_power = power)
  # 1.x accepted 'df.num.within' with any effect and used it only for the
  # between-within interaction; the web apps pass it on every call. Forward it
  # only where it is used, so 1.x calls keep working without a warning.
  if (!missing(df.num.within) && identical(effect, "between_within"))
    args$df_num_within <- df.num.within
  do.call(ss_buc_mixed_anova_general, args)
}

#' @rdname bucss-deprecated
#' @export
ss.power.reg1 <- function(t.observed, N, p, alpha.prior = .05, alpha.planned = .05,
                          assurance = .80, power = .80, step = .001) {
  .deprecate_once("ss.power.reg1", "'ss_buc_reg_coef'")
  ss_buc_reg_coef(t_observed = t.observed, N = N, p = p,
                  alpha_prior = alpha.prior, alpha_planned = alpha.planned,
                  assurance = assurance, desired_power = power,
                  random_predictors = FALSE)
}

#' @rdname bucss-deprecated
#' @export
ss.power.reg.all <- function(F.observed, N, p, alpha.prior = .05, alpha.planned = .05,
                             assurance = .80, power = .80, step = .001) {
  .deprecate_once("ss.power.reg.all", "'ss_buc_R2'")
  ss_buc_R2(F_observed = F.observed, N = N, p = p,
            alpha_prior = alpha.prior, alpha_planned = alpha.planned,
            assurance = assurance, desired_power = power,
            random_predictors = FALSE)
}

#' @rdname bucss-deprecated
#' @export
ss.power.reg.joint <- function(F.observed, N, p, p.joint, alpha.prior = .05,
                               alpha.planned = .05, assurance = .80, power = .80,
                               step = .001) {
  .deprecate_once("ss.power.reg.joint", "'ss_buc_reg_joint'")
  ss_buc_reg_joint(F_observed = F.observed, N = N, p = p, p_joint = p.joint,
                   alpha_prior = alpha.prior, alpha_planned = alpha.planned,
                   assurance = assurance, desired_power = power,
                   random_predictors = FALSE)
}

#' Positional Extraction From a Bias and Uncertainty Corrected Sample Size Result
#'
#' @description Backward-compatible positional extraction for
#'   \code{\link{bucss_plan}} objects. The deprecated 1.x functions (see
#'   \code{\link{bucss-deprecated}}) returned an unnamed two-element list,
#'   \code{list(sample_size, ncp)}, so code written for BUCSS 1.x reads
#'   \code{result[[1]]} and \code{result[[2]]}. The \code{ss_buc_*} functions
#'   return a richer object, but \code{result[[1]]} still yields the necessary
#'   sample size and \code{result[[2]]} the bias and uncertainty adjusted
#'   noncentrality parameter, so that 1.x extraction code keeps working.
#'
#'   Any other subscript (a column name, or a length other than one) falls
#'   through to the ordinary \code{data.frame} method, so \code{result[["value"]]}
#'   and \code{result$value} are unaffected.
#'
#'   The legacy reading applies only to an object that still has the shape of
#'   a single plan, as its planner returned it. An object that keeps the class
#'   without that shape (several plans stacked together, or a plan with rows
#'   removed, which some data manipulation tools leave in the class) is read
#'   as the plain \code{data.frame} it is, so \code{[[1]]} and \code{[[2]]}
#'   give its \code{term} and \code{value} columns rather than one plan's
#'   sample size and noncentrality parameter.
#'
#' @param x A \code{bucss_plan} object.
#' @param i A subscript. A single double \code{1} or \code{2}, as 1.x code
#'   writes it, selects the legacy scalar; anything else, including the
#'   integers \code{1L} and \code{2L}, is passed to the \code{data.frame}
#'   method as a column index.
#' @param ... Passed to the \code{data.frame} method.
#'
#' @return For \code{i} equal to the double \code{1} or \code{2} and an object
#'   with the shape of a single plan, a single numeric value; otherwise
#'   whatever the \code{data.frame} \code{[[} method returns.
#'
#' @keywords internal
#' @export
`[[.bucss_plan` <- function(x, i, ...) {
  # Two guards keep the legacy shim from firing on base R's own internals.
  # nargs() == 2L limits it to the single-subscript form, so the matrix-style
  # x[[i, j]] still selects a cell. is.double(i) limits it to a subscript the
  # user typed as a literal: base R's column loops (format.data.frame, str,
  # lapply, data.matrix, and friends) index with integers from seq_len(), and
  # without this they would receive the legacy scalars in place of the term
  # and value columns. A third guard, the shape check every other reader of
  # "the" sample size makes, keeps it to one plan: on a stack of plans the
  # legacy reading returned the first plan's size and a vector of parameters,
  # and on a plan with rows removed NA or an empty vector, with no condition
  # raised. (.is_plan_shape() reads the columns by name, so it does not come
  # back through this branch.)
  if (nargs() == 2L && !missing(i) && length(i) == 1L && is.double(i) &&
      i %in% c(1, 2) && .is_plan_shape(x)) {
    term <- .subset2(x, "term")
    value <- .subset2(x, "value")
    if (i == 1) return(value[term %in% .BUCSS_SIZE_TERMS][1])
    return(value[term == "ncp_adjusted"])
  }
  NextMethod()
}

#' Subset a Bias and Uncertainty Corrected Sample Size Result
#'
#' Row and column subsetting returns a plain \code{data.frame}. The class is
#' dropped first so that \code{[.data.frame}'s internal column extraction
#' (which uses \code{[[} on columns 1 and 2) cannot collide with the legacy
#' positional \code{[[} contract (see \code{\link{[[.bucss_plan}}); without
#' this, \code{head(result)} and \code{result[i, ]} would silently return the
#' legacy scalars in place of the \code{term} and \code{value} columns. A
#' subset is no longer the full planning result (its attributes and print
#' contract no longer apply), so the return is an ordinary \code{data.frame}.
#'
#' Modifying or stacking a result does the same. Assignment through
#' \code{[<-}, \code{[[<-}, \code{$<-}, and \code{names<-} (and so
#' \code{colnames<-} and \code{setNames()}), and \code{rbind()}, all return an
#' ordinary \code{data.frame} without the print, \code{tidy()},
#' \code{glance()}, or positional \code{[[} behavior. Assigning to
#' \code{result[[1]]} or \code{result[[2]]} is refused with an error, because
#' reading those subscripts gives the legacy scalars while assigning to them
#' would overwrite the \code{term} or \code{value} column.
#'
#' @param x A \code{bucss_plan} object.
#' @param ... Passed to the \code{data.frame} method.
#'
#' @return A \code{data.frame} (or vector, under the usual \code{drop} rules).
#'
#' @name sub-.bucss_plan
#' @keywords internal
#' @export
`[.bucss_plan` <- function(x, ...) {
  class(x) <- setdiff(class(x), "bucss_plan")
  x[...]
}

# Modifying or stacking a result makes it something other than one planner's
# answer, so the class (and with it the print contract, the broom verbs, the
# legacy positional [[, and the attributes describing a single design) comes
# off and an ordinary data.frame comes back. Without this, an added column
# would be invisible to the print method, an assignment through the legacy [[
# would write to a different place than it reads from, and a stack of results
# would print, tidy, and narrate as though it were the first one alone.

#' @rdname sub-.bucss_plan
#' @param i,j,value Passed to the \code{data.frame} method.
#' @keywords internal
#' @export
`[<-.bucss_plan` <- function(x, i, j, value) {
  class(x) <- setdiff(class(x), "bucss_plan")
  NextMethod()
}

#' @rdname sub-.bucss_plan
#' @keywords internal
#' @export
`[[<-.bucss_plan` <- function(x, i, j, value) {
  # Reading x[[1]] gives the legacy sample size, but assigning to it would
  # write the first *column*, so the two would mean different places and
  # x[[1]] <- x[[1]] would silently overwrite the term column. Refuse the
  # ambiguous subscript rather than corrupt the object.
  if (nargs() == 3L && !missing(i) && length(i) == 1L && is.double(i) &&
      i %in% c(1, 2)) {
    stop("A 'bucss_plan' result cannot be modified through the legacy ",
         "positional '[[': reading 'x[[", i, "]]' gives the ",
         if (i == 1) "necessary sample size" else "adjusted noncentrality parameter",
         ", but assigning to it would write the '",
         if (i == 1) "term" else "value", "' column. Assign by column name ",
         "(for example x$value) or work on a copy from as.data.frame(x).",
         call. = FALSE)
  }
  class(x) <- setdiff(class(x), "bucss_plan")
  NextMethod()
}

#' @rdname sub-.bucss_plan
#' @keywords internal
#' @export
`$<-.bucss_plan` <- function(x, name, value) {
  class(x) <- setdiff(class(x), "bucss_plan")
  NextMethod()
}

#' @rdname sub-.bucss_plan
#' @keywords internal
#' @export
`names<-.bucss_plan` <- function(x, value) {
  # Renamed columns are no longer the term/value contract the print method,
  # the broom verbs, and the legacy positional [[ read. (colnames<- and
  # setNames() come through here too.)
  class(x) <- setdiff(class(x), "bucss_plan")
  NextMethod()
}

#' @rdname sub-.bucss_plan
#' @param deparse.level Passed to the \code{data.frame} method.
#' @keywords internal
#' @export
rbind.bucss_plan <- function(..., deparse.level = 1) {
  args <- lapply(list(...), function(a) {
    if (inherits(a, "bucss_plan")) class(a) <- setdiff(class(a), "bucss_plan")
    a
  })
  do.call(rbind, c(args, list(deparse.level = deparse.level)))
}

Try the BUCSS package in your browser

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

BUCSS documentation built on Oct. 14, 2026, 1:07 a.m.