| Type: | Package |
| Title: | Sample Size Calculation for PICOT-Based Study Designs |
| Version: | 0.1.0 |
| Description: | Provides sample size calculators for the study designs covered by the PICOT framework, including cross-sectional, case-control, cohort, superiority, non-inferiority, and equivalence clinical trials, and diagnostic test accuracy studies, following Bhardwaj et al. (2024) <doi:10.4103/jfmpc.jfmpc_1675_23>. Calculations are performed using the 'epiR' package as a validated computational backend. Includes a 'shiny' application with a PICOT-based design wizard, an interactive sensitivity plot, and automatically generated Methods-section text for manuscripts. |
| License: | MIT + file LICENSE |
| Depends: | R (≥ 4.1.0) |
| Encoding: | UTF-8 |
| Imports: | bslib, epiR, ggplot2, plotly, rlang, shiny |
| RoxygenNote: | 7.3.3 |
| Suggests: | knitr, rmarkdown, testthat (≥ 3.0.0) |
| URL: | https://github.com/AtefehRashidi/PICOTsize, https://orcid.org/0009-0002-3834-3183 |
| BugReports: | https://github.com/AtefehRashidi/PICOTsize/issues |
| Config/testthat/edition: | 3 |
| VignetteBuilder: | knitr |
| NeedsCompilation: | no |
| Packaged: | 2026-09-30 10:43:45 UTC; Administrator |
| Author: | Atefeh Rashidi Pour [aut, cre] |
| Maintainer: | Atefeh Rashidi Pour <rashidiatefeh98@gmail.com> |
| Repository: | CRAN |
| Date/Publication: | 2026-10-10 10:10:25 UTC |
Adjust a raw sample size for expected dropout
Description
Studies with follow-up, non-response, or attrition need to enrol more participants than the raw formula suggests, so that enough complete observations remain at the end. This function applies the statistically correct inflation formula:
Usage
apply_dropout(n, dropout_rate = 0.2)
Arguments
n |
Numeric. The raw (uninflated) sample size, before accounting for dropout. |
dropout_rate |
Numeric between 0 and 1 (not inclusive of 1). Expected proportion of participants lost to follow-up or non-response. Defaults to 0.20 (20%), the conventional default used throughout the sample size literature. |
Details
n* = n / (1 - dropout_rate)
Note: Bhardwaj et al. (2024), the primary reference for this package, states this same formula in the text, but their worked numeric examples actually use the simpler approximation n * (1 + dropout_rate), which gives a slightly smaller (less conservative) sample size. This package follows the formula as stated in the text, not the arithmetic in the worked examples. See the package Validation vignette for a side-by-side comparison.
Value
A single integer: the dropout-adjusted sample size, rounded up to the nearest whole participant.
Examples
apply_dropout(323, dropout_rate = 0.10)
Build the dynamic parameter form for a given study design
Description
Returns the input widgets appropriate to design_key. Field inputIds are shared across designs where the underlying meaning is analogous (e.g. calc_p1 is "prevalence" for a cross-sectional study but "outcome rate in controls" for a case-control study) – this keeps server.R simple, since it can always read from the same small set of inputIds regardless of which form is showing.
Usage
build_calculator_form(design_key)
Arguments
design_key |
Character. One of the design keys produced by current_design() in server.R. |
Value
A shiny tag list of input widgets.
Sample size for a case-control study
Description
Estimates the number of cases and controls needed to detect an association between an exposure and an outcome, following Bhardwaj et al. (2024) and using epiR::epi.sscc() as the validated computational backend.
Usage
calc_casecontrol(
p_exposed_controls,
p_exposed_cases,
control_case_ratio = 1,
power = 0.8,
sig_level = 0.05,
dropout_rate = 0.2
)
Arguments
p_exposed_controls |
Numeric between 0 and 1. Proportion exposed among controls (p0 in the paper's notation). |
p_exposed_cases |
Numeric between 0 and 1. Proportion exposed among cases (p1 in the paper's notation). |
control_case_ratio |
Numeric >= 1. Number of controls per case. Defaults to 1 (equal numbers of cases and controls). |
power |
Numeric between 0 and 1. Desired study power. Defaults to 0.80 (80%), matching the paper's convention. |
sig_level |
Numeric between 0 and 1. Significance level (alpha). Defaults to 0.05. |
dropout_rate |
Numeric between 0 and 1. Defaults to 0.20. |
Details
Bhardwaj et al. (2024) express the exposure/outcome association in terms of the proportion exposed among cases and among controls (p1 and p0). epiR's epi.sscc() instead takes an odds ratio (OR). This function converts p1/p0 to an OR internally, so the user can keep thinking in the same terms as the source paper.
Value
A list with n_raw (total, before dropout adjustment), n_final (total, after dropout adjustment), the derived odds ratio, and the inputs used.
Examples
# Reproduces the worked lymphoma example in Bhardwaj et al. (2024):
# 25\% exposed in controls, 40\% exposed in cases, equal groups,
# 80\% power, 10\% dropout
calc_casecontrol(p_exposed_controls = 0.25, p_exposed_cases = 0.40,
dropout_rate = 0.10)
Sample size for a cohort study
Description
Estimates the number of exposed and unexposed subjects needed to detect a difference in incidence between two groups, following Bhardwaj et al. (2024) and using epiR::epi.sscohortc() as the validated computational backend.
Usage
calc_cohort(
incidence_unexposed,
incidence_exposed,
exposed_unexposed_ratio = 1,
power = 0.8,
sig_level = 0.05,
dropout_rate = 0.2
)
Arguments
incidence_unexposed |
Numeric between 0 and 1. Expected incidence of the outcome in the unexposed group (p0 in the paper's notation). |
incidence_exposed |
Numeric between 0 and 1. Expected incidence of the outcome in the exposed group (p1 in the paper's notation). |
exposed_unexposed_ratio |
Numeric >= 1. Number of exposed subjects per unexposed subject. Defaults to 1 (equal groups). |
power |
Numeric between 0 and 1. Desired study power. Defaults to 0.80 (80%). |
sig_level |
Numeric between 0 and 1. Significance level (alpha). Defaults to 0.05. |
dropout_rate |
Numeric between 0 and 1. Defaults to 0.20. |
Value
A list with n_raw (total, before dropout adjustment), n_final (total, after dropout adjustment), and the inputs used.
Examples
# Reproduces the worked air pollution / asthma example in
# Bhardwaj et al. (2024): 20\% incidence unexposed, 30\% incidence
# exposed, equal groups, 80\% power, 10\% dropout
calc_cohort(incidence_unexposed = 0.20, incidence_exposed = 0.30,
dropout_rate = 0.10)
Sample size for a cross-sectional study with a binary outcome
Description
Estimates the sample size needed to estimate a proportion or prevalence in a population, following Bhardwaj et al. (2024) and using epiR::epi.sssimpleestb() as the validated computational backend.
Usage
calc_crosssectional_binary(
p,
precision,
error_type = "absolute",
conf_level = 0.95,
dropout_rate = 0.2
)
Arguments
p |
Numeric between 0 and 1. Expected proportion/prevalence in the population (from prior studies or a pilot study). |
precision |
Numeric. Acceptable margin of error (absolute, in the same 0-1 scale as p, unless error_type = "relative"). |
error_type |
Character. Either "absolute" or "relative". Defaults to "absolute", matching the worked examples in Bhardwaj et al. (2024). |
conf_level |
Numeric. Confidence level, e.g. 0.95 for 95%. |
dropout_rate |
Numeric between 0 and 1. Expected dropout / non-response rate. Defaults to 0.20. Set to 0 to skip dropout adjustment entirely. |
Value
A list with the raw sample size (n_raw), the dropout-adjusted sample size (n_final), and the inputs used, so the result can be fed directly into report generation.
Examples
# Reproduces the worked example in Bhardwaj et al. (2024):
# prevalence 30\%, 5\% absolute precision, 95\% CI, 10\% dropout
calc_crosssectional_binary(p = 0.30, precision = 0.05,
dropout_rate = 0.10)
Sample size for a cross-sectional study with a continuous outcome
Description
Estimates the sample size needed to estimate a population mean, following Bhardwaj et al. (2024) and using epiR::epi.sssimpleestc() as the validated computational backend.
Usage
calc_crosssectional_continuous(
mean,
sd,
precision,
error_type = "absolute",
conf_level = 0.95,
dropout_rate = 0.2
)
Arguments
mean |
Numeric. Expected mean of the outcome (from prior studies or a pilot study). Required by the epiR backend even when error_type = "absolute". |
sd |
Numeric. Expected standard deviation of the outcome. |
precision |
Numeric. Acceptable margin of error. In the same units as sd if error_type = "absolute", or as a fraction of mean if error_type = "relative". |
error_type |
Character. Either "absolute" or "relative". Defaults to "absolute", matching the worked examples in Bhardwaj et al. (2024). Note this differs from epiR's own default ("relative") – we override it here. |
conf_level |
Numeric. Confidence level, e.g. 0.95 for 95%. |
dropout_rate |
Numeric between 0 and 1. Defaults to 0.20. |
Value
A list with the same structure as [calc_crosssectional_binary()].
Examples
# Reproduces the worked SBP example in Bhardwaj et al. (2024):
# mean SBP unspecified in the paper's formula, SD = 3 mmHg,
# precision = 0.5 mmHg, 95\% CI, 10\% dropout
calc_crosssectional_continuous(mean = 120, sd = 3, precision = 0.5,
dropout_rate = 0.10)
Sample size to estimate the sensitivity and specificity of a diagnostic test
Description
Estimates the sample size needed to evaluate a diagnostic test's accuracy, using epiR::epi.ssdxsesp() as the validated computational backend. That function implements the method of Buderer (1996) and Hajian-Tilaki (2014), which calculates the required n for sensitivity and for specificity separately, then takes the LARGER of the two as the total study sample size – because the same group of subjects is used to estimate both simultaneously, not two separate samples.
Usage
calc_diagnostic_accuracy(
expected_sensitivity,
expected_specificity,
prevalence,
precision,
error_type = "absolute",
conf_level = 0.95,
dropout_rate = 0
)
Arguments
expected_sensitivity |
Numeric between 0 and 1. Prior estimate of the test's sensitivity. |
expected_specificity |
Numeric between 0 and 1. Prior estimate of the test's specificity. |
prevalence |
Numeric between 0 and 1. Expected prevalence of the disease/condition in the study population. |
precision |
Numeric. Acceptable margin of error for the estimate (absolute, unless error_type = "relative"). |
error_type |
Character. Either "absolute" or "relative". Defaults to "absolute", matching the worked example in Bhardwaj et al. (2024). |
conf_level |
Numeric. Confidence level, e.g. 0.95 for 95%. |
dropout_rate |
Numeric between 0 and 1. Defaults to 0, since Bhardwaj et al. (2024) do not apply a dropout adjustment to their diagnostic test example. Set to a positive value if your study design calls for one. |
Details
Note this differs from Bhardwaj et al. (2024), who calculate the two requirements separately and add them together. Summing effectively assumes two independent studies, which is not how diagnostic accuracy studies are actually run. This package follows the epiR/Buderer approach (the larger of the two), as the methodologically correct one. See the package Validation vignette for a side-by-side comparison.
Value
A list with n_sensitivity, n_specificity, n_raw (the larger of the two – the actual required enrolment), n_final (dropout-adjusted), and the inputs used.
Examples
# Reproduces the worked hypertension test example in
# Bhardwaj et al. (2024): sensitivity 80\%, specificity 90\%,
# prevalence 20\%, 5\% absolute margin of error
calc_diagnostic_accuracy(expected_sensitivity = 0.80,
expected_specificity = 0.90,
prevalence = 0.20, precision = 0.05)
Sample size for an equivalence trial (binary outcome)
Description
Estimates the sample size needed to demonstrate that two treatments are, for practical purposes, equally effective, using epiR::epi.ssequb() as the validated computational backend.
Usage
calc_trial_equivalence(
p_standard,
p_new,
delta,
power = 0.8,
sig_level = 0.05,
treat_control_ratio = 1,
dropout_rate = 0.2
)
Arguments
p_standard |
Numeric between 0 and 1. Outcome rate in the standard/control treatment group. |
p_new |
Numeric between 0 and 1. Outcome rate in the new treatment group. |
delta |
Numeric >= 0. Equivalence limit – the maximum difference in either direction still considered "equivalent". |
power |
Numeric between 0 and 1. Desired study power. Defaults to 0.80. |
sig_level |
Numeric between 0 and 1. Significance level (alpha). Defaults to 0.05. |
treat_control_ratio |
Numeric >= 1. Number in the treatment group per subject in the control group. Defaults to 1. |
dropout_rate |
Numeric between 0 and 1. Defaults to 0.20. |
Value
A list with n_raw, n_final, and the inputs used.
Examples
calc_trial_equivalence(p_standard = 0.45, p_new = 0.45,
delta = 0.10)
Sample size for a non-inferiority trial (binary outcome)
Description
Estimates the sample size needed to demonstrate that a new treatment is not unacceptably worse than a standard treatment, using epiR::epi.ssninfb() as the validated computational backend.
Usage
calc_trial_noninferiority(
p_standard,
p_new,
delta,
power = 0.8,
sig_level = 0.05,
treat_control_ratio = 1,
dropout_rate = 0.2
)
Arguments
p_standard |
Numeric between 0 and 1. Outcome rate in the standard/control treatment group. |
p_new |
Numeric between 0 and 1. Outcome rate in the new treatment group. |
delta |
Numeric >= 0. Non-inferiority margin – the maximum acceptable drop in outcome rate for the new treatment to still be considered non-inferior. This is the parameter most often mis-specified, so double-check it reflects a clinically (not just statistically) meaningful difference. |
power |
Numeric between 0 and 1. Desired study power. Defaults to 0.80. |
sig_level |
Numeric between 0 and 1. Significance level (alpha). Defaults to 0.05. |
treat_control_ratio |
Numeric >= 1. Number in the treatment group per subject in the control group. Defaults to 1. |
dropout_rate |
Numeric between 0 and 1. Defaults to 0.20. |
Value
A list with n_raw, n_final, and the inputs used.
Examples
calc_trial_noninferiority(p_standard = 0.45, p_new = 0.45,
delta = 0.10)
Sample size for a superiority trial (binary outcome)
Description
Estimates the sample size needed to demonstrate that a new treatment is better than a standard treatment, following Bhardwaj et al. (2024) and using epiR::epi.sssupb() as the validated computational backend.
Usage
calc_trial_superiority(
p_standard,
p_new,
delta,
power = 0.8,
sig_level = 0.05,
sided_test = 2,
treat_control_ratio = 1,
dropout_rate = 0.2
)
Arguments
p_standard |
Numeric between 0 and 1. Outcome rate in the standard/control treatment group. |
p_new |
Numeric between 0 and 1. Outcome rate in the new treatment group. |
delta |
Numeric >= 0. Superiority margin – the minimum difference researchers want to be able to detect. |
power |
Numeric between 0 and 1. Desired study power. Defaults to 0.80. |
sig_level |
Numeric between 0 and 1. Significance level (alpha). Defaults to 0.05. |
sided_test |
Either 1 or 2. Bhardwaj et al. (2024) use a one-sided test for superiority trials (the traditional convention). However, epiR's own documentation notes that "regulatory agencies and most clinical trial guidelines recommend two-sided tests for superiority trials" – this is a genuine, unresolved difference of opinion in the literature, not a bug. Defaults to 2 (two-sided), matching current regulatory guidance; set to 1 to reproduce the paper's own worked example. |
treat_control_ratio |
Numeric >= 1. Number in the treatment group per subject in the control group. Defaults to 1. |
dropout_rate |
Numeric between 0 and 1. Defaults to 0.20. |
Value
A list with n_raw, n_final, and the inputs used.
Examples
# Reproduces the worked cancer survival example in
# Bhardwaj et al. (2024) -- use sided_test = 1 to match their
# one-sided convention exactly
calc_trial_superiority(p_standard = 0.45, p_new = 0.61,
delta = 0.10, sided_test = 1)
Generate a Methods-section paragraph from a sample size result
Description
Takes the list returned by any of the calc_* functions in this package and produces a ready-to-use paragraph describing the sample size calculation, written in the style of a Methods section, following Bhardwaj et al. (2024)'s own reporting conventions.
Usage
generate_report_text(result)
Arguments
result |
A list, as returned by calc_crosssectional_binary(), calc_crosssectional_continuous(), calc_casecontrol(), calc_cohort(), calc_trial_superiority(), calc_trial_noninferiority(), calc_trial_equivalence(), or calc_diagnostic_accuracy(). |
Value
A single character string containing the report paragraph.
Examples
result <- calc_crosssectional_binary(p = 0.30, precision = 0.05,
dropout_rate = 0.10)
generate_report_text(result)
The PICOTsize bslib theme
Description
The PICOTsize bslib theme
Usage
picotsize_theme
Format
An object of class bs_theme_with_preset (inherits from bs_version_5, bs_theme, sass_bundle) of length 1.
The list of parameters that can be varied on the sensitivity plot, for a given design
Description
The list of parameters that can be varied on the sensitivity plot, for a given design
Usage
plot_parameter_choices(design_key)
Arguments
design_key |
Character. One of the design keys produced by current_design() in server.R. |
Value
A named character vector suitable for selectInput(choices = ...). Values are generic field names used internally by server.R (not the form's inputIds), so the plot can vary one field in isolation. Parameters available for the sensitivity plot, by design
Launch the PICOTsize Shiny app
Description
Launch the PICOTsize Shiny app
Usage
run_app()
Value
No return value. Launches the Shiny app in a browser or the RStudio Viewer pane.
Examples
if (interactive()){
run_app()
}
Main Shiny server function for PICOTsize
Description
Main Shiny server function for PICOTsize
Usage
server(input, output, session)
Arguments
input, output, session |
Standard Shiny server arguments. |
The title footer
Description
The title footer
Usage
tile_footer()
The title header
Description
The title header
Usage
tile_header()
The ui
Description
The ui
Usage
ui()
UI for the Calculator tab
Description
This tab's parameter form changes depending on which study design was chosen in the Wizard tab, so most of it is built dynamically on the server side (see server.R and ui_calculator_forms.R). This function only lays out the static skeleton: a slot for the dynamic input form, a results card, an interactive plot, and the auto-generated report text.
Usage
ui_calculator()
Value
A shiny tag list, ready to be placed inside a nav_panel().
UI for the Validation tab
Description
A static comparison table showing this package's output against the worked numeric examples in Bhardwaj et al. (2024), along with a short explanation of every case where the two differ and why.
Usage
ui_validation()
Value
A shiny tag list, ready to be placed inside a nav_panel().
UI for the PICOT Wizard tab
Description
Builds the step-by-step questionnaire that walks the user through the PICOT framework and determines which study design (and therefore which calculator form) applies. This is a UI-building function, not a static object, so it can be called from ui.R.
Usage
ui_wizard()
Value
A shiny tag list, ready to be placed inside a nav_panel().
The static validation dataset used by the table above
Description
Kept as its own small function (rather than inline in server.R) so it can also be reused by tests, if needed later.
Usage
validation_dataset()
Value
A data.frame. Static validation comparsion data