diff --git a/NAMESPACE b/NAMESPACE index 400a054..a4f2994 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -41,6 +41,7 @@ S3method(vec_ptype_full,projectable_col) S3method(vec_ptype_full,projectable_col_row) export("col_shadow<-") export(col_binomial) +export(col_binomial_unpack) export(col_binomial_vec) export(col_freq) export(col_row) diff --git a/R/col_binomial.R b/R/col_binomial.R index 85be5ef..2334a28 100644 --- a/R/col_binomial.R +++ b/R/col_binomial.R @@ -255,3 +255,79 @@ col_binomial_vec <- function(x, summarised = TRUE ) } + +#' Unpack a col_binomial object into a vector or dataframe +#' +#' This function converts the output of `col_binomial()` into either a numeric +#' vector (default) or a dataframe, depending on user preference. When returning +#' a dataframe, you can specify which fields to include, such as confidence +#' intervals. +#' +#' @param x A `projectable_col_binomial` object created by `col_binomial()` +#' @param output Character string specifying the output format. Either "vector" +#' (default) or "dataframe". +#' @param fields Character vector specifying which fields to include when +#' `output = "dataframe"`. Options include "p", "n", "N", "ci_lower", +#' "ci_upper", "ci_error", "population", and "note". Default is +#' `c("p", "ci_lower", "ci_upper")`. +#' +#' @return Either a numeric vector (when `output = "vector"`) containing the +#' point estimates, or a dataframe (when `output = "dataframe"`) containing +#' the requested fields. +#' @export +#' +#' @examples +#' # Create a binomial proportion +#' b_trials <- stats::rbinom(1000, 1, 0.5) +#' result <- col_binomial(b_trials) +#' +#' # Extract as vector (default) +#' col_binomial_unpack(result) +#' +#' # Extract as dataframe with confidence intervals +#' col_binomial_unpack(result, output = "dataframe") +#' +#' # Extract as dataframe with custom fields +#' col_binomial_unpack(result, output = "dataframe", fields = c("p", "n", "N")) +#' +col_binomial_unpack <- function(x, + output = c("vector", "dataframe"), + fields = c("p", "ci_lower", "ci_upper")) { + # Check input + if (!is_col_binomial(x)) { + stop("`x` must be a projectable_col_binomial object", call. = FALSE) + } + + # Match output argument + output <- match.arg(output) + + # Return vector by default + if (output == "vector") { + return(vctrs::field(x, "p")) + } + + # Return dataframe with requested fields + if (output == "dataframe") { + # Available fields in col_binomial + available_fields <- c("n", "N", "population", "ci_error", "p", "ci_lower", "ci_upper", "note") + + # Check that requested fields are valid + invalid_fields <- setdiff(fields, available_fields) + if (length(invalid_fields) > 0) { + stop( + "Invalid field(s): ", paste(invalid_fields, collapse = ", "), + ". Available fields are: ", paste(available_fields, collapse = ", "), + call. = FALSE + ) + } + + # Extract requested fields + result <- lapply(fields, function(field) { + vctrs::field(x, field) + }) + names(result) <- fields + + # Convert to dataframe + return(tibble::as_tibble(result)) + } +} diff --git a/man/col_binomial_unpack.Rd b/man/col_binomial_unpack.Rd new file mode 100644 index 0000000..393cc4f --- /dev/null +++ b/man/col_binomial_unpack.Rd @@ -0,0 +1,49 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/col_binomial.R +\name{col_binomial_unpack} +\alias{col_binomial_unpack} +\title{Unpack a col_binomial object into a vector or dataframe} +\usage{ +col_binomial_unpack( + x, + output = c("vector", "dataframe"), + fields = c("p", "ci_lower", "ci_upper") +) +} +\arguments{ +\item{x}{A \code{projectable_col_binomial} object created by \code{col_binomial()}} + +\item{output}{Character string specifying the output format. Either "vector" +(default) or "dataframe".} + +\item{fields}{Character vector specifying which fields to include when +\code{output = "dataframe"}. Options include "p", "n", "N", "ci_lower", +"ci_upper", "ci_error", "population", and "note". Default is +\code{c("p", "ci_lower", "ci_upper")}.} +} +\value{ +Either a numeric vector (when \code{output = "vector"}) containing the + point estimates, or a dataframe (when \code{output = "dataframe"}) containing + the requested fields. +} +\description{ +This function converts the output of \code{col_binomial()} into either a numeric +vector (default) or a dataframe, depending on user preference. When returning +a dataframe, you can specify which fields to include, such as confidence +intervals. +} +\examples{ +# Create a binomial proportion +b_trials <- stats::rbinom(1000, 1, 0.5) +result <- col_binomial(b_trials) + +# Extract as vector (default) +col_binomial_unpack(result) + +# Extract as dataframe with confidence intervals +col_binomial_unpack(result, output = "dataframe") + +# Extract as dataframe with custom fields +col_binomial_unpack(result, output = "dataframe", fields = c("p", "n", "N")) + +} diff --git a/man/col_binomial_vec.Rd b/man/col_binomial_vec.Rd new file mode 100644 index 0000000..4a7977b --- /dev/null +++ b/man/col_binomial_vec.Rd @@ -0,0 +1,57 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/col_binomial.R +\name{col_binomial_vec} +\alias{col_binomial_vec} +\title{Calculate binomial proportions from a vector} +\usage{ +col_binomial_vec( + x, + success_value = 1, + range = c(0, 1), + ci_error = 0.05, + population = Inf, + method = "agresti.coull" +) +} +\arguments{ +\item{x}{A numeric or integer vector containing the data. Can include NA values.} + +\item{success_value}{The value(s) that count as "success". Can be a single value +or a vector of values. Default is 1.} + +\item{range}{The range of values to include in the calculation. Values outside +this range are excluded (treated as NA). Default is c(0, 1). Set to NULL to +include all non-NA values.} + +\item{ci_error}{A numeric vector, the error to be used for calculating +confidence intervals. Default is 0.05.} + +\item{population}{A numeric vector, the number of individuals in the +population to be used for calculating confidence intervals. Default is Inf.} + +\item{method}{The name of a method to be passed through to \code{asbio::ci.p()} for +parameter estimation. The default is "agresti.coull", but other options +include "asymptotic", "score", "LR" and "exact". See \code{asbio::ci.p()} for details.} +} +\value{ +An S3 vector of class \code{projectable_col_binomial} +} +\description{ +A wrapper function for \code{col_binomial()} that takes a single vector and +automatically calculates the number of successes and trials. This is +particularly useful when working with data that contains missing values. +} +\examples{ +# Calculate proportion of 1s in a 0-1 vector with missing data +x <- c(0, 1, 1, 0, NA, 1, 0, 1) +col_binomial_vec(x) + +# Calculate proportion of 2s in a 1-2 vector +y <- c(1, 2, 2, 1, NA, 2, 1, 2) +col_binomial_vec(y, success_value = 2, range = c(1, 2)) + +# Calculate proportion with multiple success values +z <- c(1, 2, 3, 4, 5, NA, 2, 3) +col_binomial_vec(z, success_value = c(2, 3), range = c(1, 5)) + +} diff --git a/tests/testthat/test-col.R b/tests/testthat/test-col.R index d010d15..29509e7 100644 --- a/tests/testthat/test-col.R +++ b/tests/testthat/test-col.R @@ -387,3 +387,115 @@ testthat::test_that("prj_project_col", { ) }) + +# col_binomial_unpack ----------------------------------------------------- + +testthat::test_that("col_binomial_unpack: basic vector output", { + # Create a binomial proportion + b_trials <- c(1, 1, 0, 1, 0, 1, 1, 0, 1, 1) + result <- col_binomial(b_trials) + + # Test default vector output + unpacked <- col_binomial_unpack(result) + + # Should return a numeric vector + testthat::expect_type(unpacked, "double") + testthat::expect_length(unpacked, 1) + + # Should equal the p value + expected_p <- vctrs::field(result, "p") + testthat::expect_identical(unpacked, expected_p) +}) + +testthat::test_that("col_binomial_unpack: dataframe output with defaults", { + # Create a binomial proportion + result <- col_binomial(7, 10, summarised = TRUE) + + # Test dataframe output with default fields + unpacked <- col_binomial_unpack(result, output = "dataframe") + + # Should return a tibble + testthat::expect_s3_class(unpacked, "tbl_df") + + # Should have default fields: p, ci_lower, ci_upper + testthat::expect_identical(names(unpacked), c("p", "ci_lower", "ci_upper")) + + # Values should match the original + testthat::expect_identical(unpacked$p, vctrs::field(result, "p")) + testthat::expect_identical(unpacked$ci_lower, vctrs::field(result, "ci_lower")) + testthat::expect_identical(unpacked$ci_upper, vctrs::field(result, "ci_upper")) +}) + +testthat::test_that("col_binomial_unpack: dataframe output with custom fields", { + # Create a binomial proportion + result <- col_binomial(7, 10, summarised = TRUE) + + # Test dataframe output with custom fields + unpacked <- col_binomial_unpack(result, output = "dataframe", fields = c("p", "n", "N")) + + # Should return a tibble with requested fields + testthat::expect_s3_class(unpacked, "tbl_df") + testthat::expect_identical(names(unpacked), c("p", "n", "N")) + + # Values should match the original + testthat::expect_identical(unpacked$p, vctrs::field(result, "p")) + testthat::expect_identical(unpacked$n, vctrs::field(result, "n")) + testthat::expect_identical(unpacked$N, vctrs::field(result, "N")) +}) + +testthat::test_that("col_binomial_unpack: all available fields", { + # Create a binomial proportion with all parameters + result <- col_binomial(5, 20, ci_error = 0.01, population = 100, summarised = TRUE) + + # Test with all available fields + all_fields <- c("n", "N", "population", "ci_error", "p", "ci_lower", "ci_upper", "note") + unpacked <- col_binomial_unpack(result, output = "dataframe", fields = all_fields) + + # Should have all fields + testthat::expect_identical(names(unpacked), all_fields) + + # Check a few values + testthat::expect_identical(unpacked$n, vctrs::field(result, "n")) + testthat::expect_identical(unpacked$population, vctrs::field(result, "population")) + testthat::expect_identical(unpacked$ci_error, vctrs::field(result, "ci_error")) +}) + +testthat::test_that("col_binomial_unpack: error on invalid input", { + # Should error if input is not a col_binomial + testthat::expect_error( + col_binomial_unpack(1:10), + "must be a projectable_col_binomial" + ) + + testthat::expect_error( + col_binomial_unpack(col_freq(1, 2, summarised = TRUE)), + "must be a projectable_col_binomial" + ) +}) + +testthat::test_that("col_binomial_unpack: error on invalid fields", { + result <- col_binomial(7, 10, summarised = TRUE) + + # Should error on invalid field names + testthat::expect_error( + col_binomial_unpack(result, output = "dataframe", fields = c("p", "invalid_field")), + "Invalid field" + ) +}) + +testthat::test_that("col_binomial_unpack: works with col_binomial_vec", { + # Test integration with col_binomial_vec + x <- c(0, 1, 1, 0, NA, 1, 0, 1) + result <- col_binomial_vec(x) + + # Should work with vector output + unpacked_vector <- col_binomial_unpack(result) + testthat::expect_type(unpacked_vector, "double") + testthat::expect_length(unpacked_vector, 1) + + # Should work with dataframe output + unpacked_df <- col_binomial_unpack(result, output = "dataframe") + testthat::expect_s3_class(unpacked_df, "tbl_df") + testthat::expect_true(all(c("p", "ci_lower", "ci_upper") %in% names(unpacked_df))) +}) +