diff --git a/.Rbuildignore b/.Rbuildignore index e9f18f2..91adb56 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -7,3 +7,6 @@ ^\.travis\.yml$ Readme* .github/ +^CLAUDE\.md$ +^\.claude$ +^\.idea$ diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..708da9b --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,28 @@ +## Summary + + + +## Changes + + + +- + +## Checklist + +- [ ] `devtools::test()` passes locally +- [ ] `devtools::check()` passes with no ERRORs or WARNINGs +- [ ] Documentation updated (if applicable) +- [ ] Tests added/updated (if applicable) +- [ ] Backward compatible (no breaking changes to existing behavior) + +## Type of Change + +- [ ] Bug fix (non-breaking change that fixes an issue) +- [ ] New feature (non-breaking change that adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] Documentation update + +## Additional Notes + + diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index f26eb2c..7be84cf 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -1,40 +1,66 @@ -# Workflow derived from https://github.com/r-lib/actions/tree/master/examples +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help on: push: - branches: [main, master] + branches: [main, master, develop] pull_request: - branches: [main, master] + branches: [main, master, develop] name: R-CMD-check +permissions: read-all + jobs: R-CMD-check: - runs-on: ubuntu-latest + runs-on: ${{ matrix.config.os }} + + name: ${{ matrix.config.os }} (${{ matrix.config.r }}) + + strategy: + fail-fast: false + matrix: + config: + - {os: ubuntu-latest, r: 'devel'} + - {os: ubuntu-latest, r: 'release'} + - {os: ubuntu-latest, r: 'oldrel-1'} + - {os: macos-latest, r: 'release'} + - {os: windows-latest, r: 'release'} + env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} R_KEEP_PKG_SOURCE: yes + steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 + + - uses: r-lib/actions/setup-pandoc@v2 - uses: r-lib/actions/setup-r@v2 with: + r-version: ${{ matrix.config.r }} + http-user-agent: ${{ matrix.config.http-user-agent }} use-public-rspm: true - uses: r-lib/actions/setup-r-dependencies@v2 with: - extra-packages: rcmdcheck + extra-packages: any::rcmdcheck + needs: check - uses: r-lib/actions/check-r-package@v2 + with: + upload-snapshots: true + build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")' + args: 'c("--no-manual", "--as-cran")' - name: Show testthat output if: always() - run: find check -name 'testthat.Rout*' -exec cat '{}' \; || true + run: | + find check -name 'testthat.Rout*' -exec cat '{}' \; || true shell: bash - name: Upload check results if: failure() - uses: actions/upload-artifact@main + uses: actions/upload-artifact@v4 with: - name: ${{ runner.os }}-r${{ matrix.config.r }}-results + name: ${{ matrix.config.os }}-r${{ matrix.config.r }}-results path: check diff --git a/.github/workflows/code-quality.yaml b/.github/workflows/code-quality.yaml new file mode 100644 index 0000000..bb9a83b --- /dev/null +++ b/.github/workflows/code-quality.yaml @@ -0,0 +1,59 @@ +# Comprehensive code quality checks +# Includes linting, spell checking, and documentation validation +on: + push: + branches: [main, master, develop] + pull_request: + branches: [main, master, develop] + +name: code-quality + +permissions: read-all + +jobs: + quality-checks: + runs-on: ubuntu-latest + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + + steps: + - uses: actions/checkout@v5 + + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + + - name: Install R packages + run: | + renv::install(c("lintr", "spelling", "devtools")) + shell: Rscript {0} + + - name: Lint R Code + run: | + lints <- lintr::lint_package() + if (length(lints) > 0) { + print(lints) + cat("::warning::Found", length(lints), "lint issues\n") + } else { + cat("✅ No lint issues found\n") + } + shell: Rscript {0} + + - name: Check Spelling + run: | + spelling_errors <- spelling::spell_check_package() + if (nrow(spelling_errors) > 0) { + print(spelling_errors) + cat("::warning::Found", nrow(spelling_errors), "potential spelling errors\n") + } else { + cat("✅ No spelling errors found\n") + } + shell: Rscript {0} + + - name: Quality Summary + run: | + cat("## Code Quality Summary\n", file = "quality_summary.md") + cat("- ✅ Lint checks completed\n", file = "quality_summary.md", append = TRUE) + cat("- ✅ Spell checks completed\n", file = "quality_summary.md", append = TRUE) + cat(readLines("quality_summary.md"), sep = "\n") + shell: Rscript {0} \ No newline at end of file diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 0000000..feac9c4 --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,35 @@ +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples +# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help +on: + push: + branches: [main, master, develop] + pull_request: + branches: [main, master, develop] + +name: lint + +permissions: read-all + +jobs: + lint: + runs-on: ubuntu-latest + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + + steps: + - uses: actions/checkout@v4 + + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::lintr, local::. + needs: lint + + - name: Lint + run: lintr::lint_package() + shell: Rscript {0} + env: + LINTR_ERROR_ON_LINT: false diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml new file mode 100644 index 0000000..45d776a --- /dev/null +++ b/.github/workflows/pkgdown.yaml @@ -0,0 +1,58 @@ +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples +# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help +on: + push: + branches: [main, master, develop] + pull_request: + branches: [main, master, develop] + release: + types: [published] + workflow_dispatch: + +name: pkgdown + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pkgdown-${{ github.event_name != 'pull_request' && github.ref || github.run_id }} + cancel-in-progress: ${{ github.event_name != 'pull_request' }} + +jobs: + pkgdown: + runs-on: ubuntu-latest + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + permissions: + contents: read + pages: write + id-token: write + steps: + - uses: actions/checkout@v5 + + - uses: r-lib/actions/setup-pandoc@v2 + + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::pkgdown, local::. + needs: website + + - name: Build site + run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) + shell: Rscript {0} + + - name: Upload to GitHub pages + if: github.event_name != 'pull_request' + uses: actions/upload-pages-artifact@v3 + with: + path: "docs" + + - name: Deploy to GitHub pages 🚀 + if: github.event_name != 'pull_request' + uses: actions/deploy-pages@v4 \ No newline at end of file diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..a4200b4 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,63 @@ +# Workflow for CRAN release automation +# Triggered on GitHub releases to streamline CRAN submission process +on: + release: + types: [published] + +name: CRAN-release + +permissions: read-all + +jobs: + release: + runs-on: ubuntu-latest + + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + + steps: + - uses: actions/checkout@v4 + + - uses: r-lib/actions/setup-pandoc@v2 + + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::rcmdcheck, any::devtools + needs: check + + - name: Full CRAN check + run: | + rcmdcheck::rcmdcheck( + args = c("--no-manual", "--as-cran", "--run-donttest"), + build_args = c("--no-manual", "--compact-vignettes=gs+qpdf"), + error_on = "warning", + check_dir = "check" + ) + shell: Rscript {0} + + - name: Build source package + run: R CMD build . + + - name: Upload CRAN package + if: success() + uses: actions/upload-artifact@v4 + with: + name: cran-package + path: "*.tar.gz" + retention-days: 30 + + - name: Generate release notes + run: | + echo "## CRAN Release Package Built Successfully" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "✅ Full R CMD check passed with --as-cran" >> $GITHUB_STEP_SUMMARY + echo "✅ Source package built: \`$(ls *.tar.gz)\`" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "📦 **Next Steps:**" >> $GITHUB_STEP_SUMMARY + echo "1. Download the source package from artifacts" >> $GITHUB_STEP_SUMMARY + echo "2. Submit to CRAN via web form or \`devtools::release()\`" >> $GITHUB_STEP_SUMMARY + echo "3. Monitor CRAN checks at https://cran.r-project.org/web/checks/" >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml new file mode 100644 index 0000000..477fdce --- /dev/null +++ b/.github/workflows/test-coverage.yaml @@ -0,0 +1,60 @@ +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples +# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help +on: + push: + branches: [main, master, develop] + pull_request: + branches: [main, master, develop] + +name: test-coverage + +permissions: read-all + +jobs: + test-coverage: + runs-on: ubuntu-latest + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + + steps: + - uses: actions/checkout@v4 + + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::covr, any::xml2 + needs: coverage + + - name: Test coverage + run: | + cov <- covr::package_coverage( + quiet = FALSE, + clean = FALSE, + install_path = file.path(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "package") + ) + covr::to_cobertura(cov) + shell: Rscript {0} + + - uses: codecov/codecov-action@v4 + with: + fail_ci_if_error: false + file: ./cobertura.xml + plugin: noop + disable_search: true + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Show testthat output + if: always() + run: | + find '${{ runner.temp }}/package' -name 'testthat.Rout*' -exec cat '{}' \; || true + shell: bash + + - name: Upload test results + if: failure() + uses: actions/upload-artifact@v4 + with: + name: coverage-test-failures + path: ${{ runner.temp }}/package diff --git a/.gitignore b/.gitignore index 1fb9175..e407630 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,9 @@ doc Meta /doc/ /Meta/ + +# IDE files +.idea/ + +# Claude Code files +.claude/ diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.lintr b/.lintr new file mode 100644 index 0000000..ff97b93 --- /dev/null +++ b/.lintr @@ -0,0 +1 @@ +exclusions: list("R/utils-pipe.R", "tests/testthat.R") \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 63fbeb8..0000000 --- a/.travis.yml +++ /dev/null @@ -1,5 +0,0 @@ -# R for travis: see documentation at https://docs.travis-ci.com/user/languages/r - -language: R -cache: packages -warnings_are_errors: false diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..3077470 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,676 @@ +# CLAUDE.md - besthr Development Guide + +## Project Overview + +**besthr** is an R package for generating bootstrap estimation distributions of HR (Hypersensitive Response) data from plant pathology experiments. It creates publication-ready visualizations showing scored HR experiments with bootstrap confidence intervals. + +- **Version**: 0.3.2 +- **CRAN Status**: Published +- **License**: MIT +- **DOI**: 10.5281/zenodo.3374507 + +## Quick Start + +```r +# Restore renv environment +renv::restore() + +# Install development dependencies +renv::install(c("devtools", "testthat")) + +# Common development commands +devtools::load_all() # Load package for interactive use +devtools::test() # Run testthat tests +devtools::check() # Full R CMD check +devtools::document() # Regenerate documentation +``` + +## Architecture + +### Core Object: `hrest` + +The package centers around the `hrest` S3 class returned by `estimate()`: + +``` +hrest object structure: +├── control # Character: control group name (default "A") +├── group_means # tibble: mean rank per group +├── ranked_data # tibble: data with rank column (tech reps averaged) +├── original_data # tibble: raw input data with rank column +├── bootstraps # tibble: bootstrap mean ranks per iteration/group +├── ci # tibble: confidence interval bounds per group +├── nits # Integer: number of bootstrap iterations +├── low # Numeric: lower quantile bound +├── high # Numeric: upper quantile bound +├── group_n # tibble: sample size per group +└── column_info # list: quosures of input column names +``` + +### Data Flow + +``` +User Data (tibble) + │ + ▼ + estimate() + │ + ├──► add_rank() # Compute score ranks + │ + ├──► [if tech reps] # Average technical replicates + │ group_by + summarize + │ + ├──► bootstrap_dist() # N iterations of resampling + │ └── bstrap_sample() # Single bootstrap iteration + │ + └──► conf_intervals() # Compute quantiles + │ + ▼ + hrest object + │ + ▼ + plot.hrest() + │ + ├──► dot_plot() # Left panel: ranked scores + │ or + │ tech_rep_dot_plot() # Left panel: raw scores by rep + │ + └──► ggridges density # Right panel: bootstrap dist + │ + ▼ + patchwork layout +``` + +### Key Files + +| File | Purpose | +|------|---------| +| `R/functions.R` | Core logic: estimate(), print.hrest(), bootstrap helpers | +| `R/plot-config.R` | Configuration system: besthr_plot_config(), besthr_style() | +| `R/plot-layers.R` | Data view and composable layer functions | +| `R/plot-panels.R` | Panel builders: build_observation_panel(), build_bootstrap_panel() | +| `R/plot-hrest.R` | Main plot.hrest() and legacy helpers | +| `R/plot-raincloud.R` | Alternative visualizations | +| `R/themes.R` | Theming: theme_besthr(), color palettes, scales | +| `R/utils-pipe.R` | Re-export of magrittr pipe | +| `tests/testthat/` | Test files for all exported functions | +| `vignettes/basic-use.Rmd` | User-facing vignette | + +## README Generation (Critical) + +### **Proper README Workflow** + +The GitHub README is generated from `README.Rmd` (NOT `Readme.Rmd`). Follow this exact process: + +**📝 File Requirements:** +- **Source**: `README.Rmd` (uppercase R - GitHub standard) +- **Output**: `README.md` (uppercase R - required for GitHub display) +- **Figures**: Must go in `figures/` directory (not `README_files/figure-gfm/`) + +**🔧 Essential Setup in README.Rmd:** +```r +# REQUIRED: knitr setup chunk for figure paths +```{r setup, include=FALSE} +knitr::opts_chunk$set(fig.path = "figures/") +``` + +# REQUIRED: Badges in source Rmd (NOT just in generated .md) + +[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.3374507.svg)](https://doi.org/10.5281/zenodo.3374507) +[![R-CMD-check](https://github.com/TeamMacLean/besthr/workflows/R-CMD-check/badge.svg)](https://github.com/TeamMacLean/besthr/actions) +[![codecov](https://codecov.io/gh/TeamMacLean/besthr/branch/develop/graph/badge.svg)](https://codecov.io/gh/TeamMacLean/besthr) + +``` + +**🚀 Regeneration Commands:** +```r +# ALWAYS load package first (functions needed for examples) +devtools::load_all() +rmarkdown::render('README.Rmd') +``` + +**✅ Verification Checklist:** +- [ ] README.md shows `![](figures/...)` paths (NOT `README_files/figure-gfm/`) +- [ ] All badges appear in README.md +- [ ] Figures directory `figures/` contains all referenced images +- [ ] Git tracks `README.md` and `README.Rmd` (uppercase R) + +**❌ Common Pitfalls:** +- Using `Readme.Rmd` (lowercase) - GitHub won't find it +- Missing `fig.path = "figures/"` - creates broken figure paths +- Adding badges only to .md file - disappears on regeneration +- Not loading package first - function examples fail + +## Documentation Workflow + +### pkgdown Site Generation + +The package documentation site is automatically built and deployed via GitHub Actions: + +**🚀 Automated Deployment:** +- **Workflow**: `.github/workflows/pkgdown.yaml` +- **Triggers**: Push to main/master/develop, PRs, releases, manual dispatch +- **Output**: GitHub Pages site with full function reference and articles +- **URL**: Auto-deployed to `https://teammacLean.github.io/besthr/` + +**🔧 Local Development:** +```r +# Install pkgdown if needed +install.packages("pkgdown") + +# Build site locally (creates docs/ directory) +pkgdown::build_site() + +# Preview site +pkgdown::preview_site() +``` + +**⚙️ Configuration:** +- **Config File**: `_pkgdown.yml` - Bootstrap 5, custom navbar, URL configuration +- **Bootstrap**: Updated to Bootstrap 5 (replacing deprecated Bootstrap 3) +- **URL**: Configured for GitHub Pages deployment + +**✅ Site Contents:** +- Function reference from all `.Rd` files +- Vignette articles from `vignettes/` +- README as homepage +- Automatic linking between functions +- Search functionality + +### CRAN Release Automation + +The package includes automated CRAN release preparation: + +**🚀 Release Workflow:** +- **Workflow**: `.github/workflows/release.yaml` +- **Trigger**: GitHub release publication +- **Actions**: Full `--as-cran` check, source package build, artifact upload +- **Output**: Ready-to-submit `.tar.gz` package with validation confirmation + +**🔧 Release Process:** +```bash +# 1. Update version in DESCRIPTION +# 2. Update NEWS.md (if exists) +# 3. Create GitHub release +gh release create v0.x.y --title "Release v0.x.y" --notes "Release notes" + +# 4. Workflow automatically: +# - Runs full CRAN checks +# - Builds source package +# - Uploads as artifact + +# 5. Download artifact and submit to CRAN +``` + +## Development Guidelines + +### Backward Compatibility (Critical) + +This is a CRAN package. Any change must preserve existing behavior: + +1. **Function signatures**: Never remove or reorder existing parameters +2. **Return values**: Never change the structure of returned objects +3. **Default behavior**: New parameters must default to existing behavior +4. **Deprecation**: Use `.Deprecated()` for phasing out, never hard-remove + +### Code Style + +- Use tidyverse style (magrittr pipes, dplyr verbs) +- Quosure handling for column names via `rlang::enquos(...)` +- Document with roxygen2 (`#'` comments) +- Internal functions marked with `@keywords internal` + +### Communication Style + +**For future Claude sessions:** +- Be concise and direct +- Avoid unnecessary lists and verbose explanations +- Minimize bombast and excessive enthusiasm +- Stay factual and confirm agreement before proceeding +- Establish clear plans with defined success criteria before starting work +- Follow agreed plans without deviation - discuss any changes first +- Include testable endpoints so we know when the plan succeeds + +### Error Resolution Methodology + +**When First Attempts Fail** + +Claude's initial solutions usually work, but when they don't - avoid repeated guessing: + +**1. Stop and Investigate Before More Attempts** +- Don't make additional guesses or "try this instead" changes +- Research the actual problem systematically +- Understand what's failing and why + +**2. Research-Based Debugging** +- Use WebSearch to find official documentation for failing components +- Look up current best practices and known issues +- Verify syntax, configuration, or approach standards + +**3. Test and Debug Systematically** +- Check what's actually happening (not just what should happen) +- Test components individually to isolate the problem +- Verify basic assumptions (files exist? permissions correct? paths right?) +- Work backwards from symptoms to root cause + +**4. Evidence-Based Problem Solving** +- Each diagnostic step should inform the next +- Follow the data/process flow from input to output +- Document findings as you investigate +- Only implement fixes after understanding the root cause + +**Example Pattern**: +- ❌ First attempt fails → guess different approach → fail → try another guess → repeat +- ✅ First attempt fails → research documentation → debug actual state → find root cause → targeted fix + +**Applies to Any Technical Problem**: +- Package/dependency installation failures +- Configuration not working as expected +- Code errors that persist after initial fixes +- Development environment issues +- "It should work but doesn't" scenarios + +This prevents frustration cycles and builds reliable solutions through understanding. + +### Adding Parameters to Existing Functions + +```r +# CORRECT: Add with default that preserves behavior +plot.hrest <- function(x, ..., which = "rank_simulation", + new_param = "default_value") { + # Implementation +} + +# INCORRECT: Changing defaults or removing params +plot.hrest <- function(x, new_required_param, ...) { + # This breaks existing code! +} +``` + +### Adding New Plot Options + +When adding themes or visual options: + +1. Add parameter with backward-compatible default +2. Keep existing code path for default case +3. Add new code path for new options +4. Test that default case produces identical output + +## CI/CD Pipeline + +### GitHub Actions Workflows + +The CI/CD pipeline is now complete with the following workflows: + +- **R-CMD-check.yaml**: Matrix testing across R versions and platforms +- **test-coverage.yaml**: Coverage reporting via codecov +- **lint.yaml**: Static analysis with lintr +- **code-quality.yaml**: Comprehensive code quality checks (linting, spelling, documentation) +- **pkgdown.yaml**: Automated documentation site generation and deployment +- **release.yaml**: CRAN submission automation workflow + +### Pre-commit Checks + +Before pushing: +```r +devtools::test() # All tests pass +devtools::check() # No ERRORs or WARNINGs +``` + +## Common Tasks + +### Running Tests + +```r +# All tests +devtools::test() + +# Specific test file +testthat::test_file("tests/testthat/test-estimate.R") + +# With verbose output +devtools::test(reporter = "summary") +``` + +### Regenerating Documentation + +```r +devtools::document() +# Then check: man/*.Rd files updated +``` + +### Building for CRAN + +```r +# Full check mimicking CRAN +devtools::check(cran = TRUE) + +# Build tarball +devtools::build() +``` + +## Gotchas + +### Quosure Handling + +Column names are passed as bare symbols and captured with `enquos()`: + +```r +estimate(df, score, group) # NOT "score", "group" + +# Inside function: +quo_list <- dplyr::enquos(...) +quo_score_col <- quo_list[[1]] +# Use with !! to unquote +df %>% dplyr::mutate(rank = rank(!!quo_score_col)) +``` + +### Bootstrap Reproducibility + +Bootstrap results are stochastic. For reproducible tests: + +```r +set.seed(123) +hr <- estimate(d, score, group, nits = 100) +``` + +### ggplot2 Deprecations + +Current code uses deprecated `size` parameter in `geom_hline()`. Use `linewidth` instead: + +```r +# Old (deprecated) +geom_hline(..., size = 1) + +# New +geom_hline(..., linewidth = 1) +``` + +### tidyselect .data Usage + +The `.data` pronoun is deprecated in tidyselect contexts. Use string column names: + +```r +# Deprecated +dplyr::select(df, -.data$n) + +# Preferred +dplyr::select(df, -"n") +``` + +## Release Checklist + +1. **Update version** in DESCRIPTION +2. **Update NEWS.md** (if exists) +3. **Run full checks**: + ```r + devtools::check(cran = TRUE) + ``` +4. **Check reverse dependencies** (if any) +5. **Submit to CRAN** via `devtools::release()` or web form +6. **Tag release** in git: + ```bash + git tag -a v0.x.y -m "Release version 0.x.y" + git push origin v0.x.y + ``` + +## Sample Data + +The package includes example data generators: + +```r +make_data() # 2 groups, 10 obs each, no tech reps +make_data2() # 2 groups, 12 obs each, 3 tech reps +make_data3() # 3 groups, 12 obs each, 3 tech reps +``` + +External data files in `inst/extdata/`: +- `example-data-1.csv` +- `example-data-2.csv` +- `example-data-3.csv` + +## Upcoming Features Development Plan + +Development follows **Test-Driven Development (TDD)**: +1. Write failing tests first +2. Implement minimum code to pass +3. Refactor while keeping tests green +4. Document and update README/vignette + +### Feature 1: Significance Annotations + +**Description**: Auto-detect when bootstrap CI doesn't overlap with control mean rank and add significance markers (`*`, `**`, `***`). + +**API**: +```r +plot(hr, show_significance = TRUE) # Add * markers to significant groups +``` + +**Test Conditions** (write tests FIRST): +```r +test_that("significance annotations appear when CI doesn't overlap control", { + set.seed(123) + d <- make_data() # Groups with clear separation + hr <- estimate(d, score, group, nits = 500) + p <- plot(hr, show_significance = TRUE) + + # Check that annotation layer exists + + expect_true(any(sapply(p[[1]]$layers, function(l) inherits(l$geom, "GeomText")))) +}) + +test_that("significance annotations hidden by default", { + hr <- estimate(make_data(), score, group, nits = 100) + p <- plot(hr) + # No text annotations by default + expect_false(any(sapply(p[[1]]$layers, function(l) inherits(l$geom, "GeomText")))) +}) + +test_that("significance levels are correct", { + # * for p < 0.05, ** for p < 0.01, *** for p < 0.001 + # Based on proportion of bootstrap samples overlapping control +}) +``` + +**Implementation File**: `R/plot-panels.R` (add to observation panel) + +--- + +### Feature 2: Effect Size Annotation + +**Description**: Optional annotation showing mean rank difference between treatment and control with CI. + +**API**: +```r +plot(hr, show_effect_size = TRUE) # Add effect size annotation +``` + +**Test Conditions**: +```r +test_that("effect size annotation shows difference from control", { + hr <- estimate(make_data(), score, group, control = "A", nits = 100) + p <- plot(hr, show_effect_size = TRUE) + + # Should have annotation showing difference + expect_true(any(grepl("effect|diff", class(p), ignore.case = TRUE)) || + any(sapply(p$patches$plots, function(x) length(x$labels) > 2))) +}) + +test_that("effect size values are mathematically correct", { + set.seed(42) + hr <- estimate(make_data(), score, group, control = "A", nits = 100) + # Effect should equal mean(B) - mean(A) + expected_effect <- hr$group_means$mean[2] - hr$group_means$mean[1] + # Verify annotation contains this value +}) +``` + +**Implementation File**: `R/plot-panels.R` or new annotation layer + +--- + +### Feature 3: Forest Plot Alternative + +**Description**: Classic forest plot showing all groups as horizontal bars with CI - standard in publications. + +**API**: +```r +plot_forest(hr) +plot_forest(hr, theme = "modern", colors = "okabe_ito") +``` + +**Test Conditions**: +```r +test_that("plot_forest returns ggplot object", { + hr <- estimate(make_data(), score, group, nits = 100) + p <- plot_forest(hr) + expect_s3_class(p, "ggplot") +}) + +test_that("plot_forest shows all groups including control", { + hr <- estimate(make_data3(), score, sample, nits = 100) + p <- plot_forest(hr) + # Should show A, B, C + expect_equal(length(unique(p$data[[1]])), 3) +}) + +test_that("plot_forest respects theme and color options", { + hr <- estimate(make_data(), score, group, nits = 100) + p <- plot_forest(hr, theme = "modern", colors = "okabe_ito") + expect_s3_class(p, "ggplot") +}) + +test_that("forest plot CI bars are correct", { + set.seed(123) + hr <- estimate(make_data(), score, group, nits = 100) + p <- plot_forest(hr) + # Verify CI values match hr$ci +}) +``` + +**Implementation File**: `R/plot-forest.R` (new file) + +--- + +### Feature 4: Publication Export + +**Description**: Save plots with sensible publication defaults (300 DPI, proper dimensions, format options). + +**API**: +```r +save_besthr(hr, "figure1.png") # Auto-detect format from extension +save_besthr(hr, "figure1.pdf", width = 8, height = 6, dpi = 300) +save_besthr(hr, "figure1.tiff", dpi = 600) # High-res for print +``` + +**Test Conditions**: +```r +test_that("save_besthr creates file", { + hr <- estimate(make_data(), score, group, nits = 50) + tmp <- tempfile(fileext = ".png") + save_besthr(hr, tmp) + expect_true(file.exists(tmp)) + unlink(tmp) +}) + +test_that("save_besthr respects dimensions", { + hr <- estimate(make_data(), score, group, nits = 50) + tmp <- tempfile(fileext = ".png") + save_besthr(hr, tmp, width = 10, height = 8, dpi = 150) + info <- png::readPNG(tmp, info = TRUE) + expect_equal(attr(info, "dim")[1], 8 * 150) # height in pixels + expect_equal(attr(info, "dim")[2], 10 * 150) # width in pixels + unlink(tmp) +}) + +test_that("save_besthr supports multiple formats", { + hr <- estimate(make_data(), score, group, nits = 50) + for (ext in c(".png", ".pdf", ".svg")) { + tmp <- tempfile(fileext = ext) + expect_no_error(save_besthr(hr, tmp)) + expect_true(file.exists(tmp)) + unlink(tmp) + } +}) +``` + +**Implementation File**: `R/export.R` (new file) + +--- + +### Feature 5: Summary Table + +**Description**: Generate publication-ready results table with mean, CI, n, and effect size. + +**API**: +```r +besthr_table(hr) # Returns tibble +besthr_table(hr, format = "markdown") # For README +besthr_table(hr, format = "latex") # For papers +besthr_table(hr, format = "html") # For web +``` + +**Test Conditions**: +```r +test_that("besthr_table returns tibble with required columns", { + hr <- estimate(make_data(), score, group, nits = 100) + tbl <- besthr_table(hr) + expect_s3_class(tbl, "tbl_df") + expect_true(all(c("group", "n", "mean_rank", "ci_low", "ci_high") %in% names(tbl))) +}) + +test_that("besthr_table includes effect size for non-control groups", { + hr <- estimate(make_data(), score, group, control = "A", nits = 100) + tbl <- besthr_table(hr) + expect_true("effect_size" %in% names(tbl)) + expect_true(is.na(tbl$effect_size[tbl$group == "A"])) # Control has no effect size +}) + +test_that("besthr_table markdown format is valid", { + hr <- estimate(make_data(), score, group, nits = 100) + md <- besthr_table(hr, format = "markdown") + expect_true(grepl("\\|", md)) # Contains pipe characters + expect_true(grepl("---", md)) # Contains header separator +}) + +test_that("besthr_table values match hrest object", { + set.seed(42) + hr <- estimate(make_data(), score, group, nits = 100) + tbl <- besthr_table(hr) + # Verify means match + expect_equal(tbl$mean_rank[tbl$group == "A"], hr$group_means$mean[hr$group_means$group == "A"]) +}) +``` + +**Implementation File**: `R/table.R` (new file) + +--- + +## Development Workflow + +### For Each Feature + +1. **Create test file first**: `tests/testthat/test-.R` +2. **Run tests** (should fail): `devtools::test(filter = "")` +3. **Implement minimum code** to pass tests +4. **Run all tests**: `devtools::test()` +5. **Document**: Add roxygen2 comments, run `devtools::document()` +6. **Update README/vignette** with examples +7. **Commit**: Atomic commit with clear message + +### Ralph Loop Statement Template + +For automated development iterations: + +``` +Implement for besthr package. + +SUCCESS CONDITION: All tests in test-.R pass AND devtools::check() has 0 errors. + +Steps: +1. Read test file to understand requirements +2. Implement in R/.R +3. Run devtools::test(filter = "") +4. Fix any failures +5. Run devtools::check() +6. When all pass, output FEATURE COMPLETE +``` diff --git a/DESCRIPTION b/DESCRIPTION index 0a8fd66..4151c56 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: besthr Type: Package Title: Generating Bootstrap Estimation Distributions of HR Data -Version: 0.3.2 +Version: 0.4.0 Authors@R: c(person("Dan", "MacLean", role = c("aut","cre"), email = "dan.maclean@tsl.ac.uk") ) Description: Creates plots showing scored HR experiments and plots of distribution of means of ranks of HR score from bootstrapping. Authors (2019) . @@ -12,14 +12,19 @@ Imports: dplyr, ggplot2, ggridges, + grDevices, + grid, magrittr, patchwork, rlang, stringr, - tibble -RoxygenNote: 7.1.2 -Suggests: + tibble, + viridisLite +RoxygenNote: 7.3.3 +Suggests: knitr, rmarkdown, - readr + readr, + testthat (>= 3.0.0) +Config/testthat/edition: 3 VignetteBuilder: knitr diff --git a/NAMESPACE b/NAMESPACE index 61f2476..da3c794 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,12 +1,39 @@ # Generated by roxygen2: do not edit by hand S3method(plot,hrest) +S3method(print,besthr_data_view) +S3method(print,besthr_plot_config) S3method(print,hrest) export("%>%") +export(apply_besthr_theme) +export(besthr_data_view) +export(besthr_palette) +export(besthr_plot_config) +export(besthr_style) +export(besthr_table) +export(build_bootstrap_panel) +export(build_observation_panel) +export(compose_besthr_panels) +export(compute_effect_size) +export(compute_significance) +export(derive_ci_colors) export(estimate) +export(layer_bootstrap_density) +export(layer_group_means) +export(layer_ranked_dots) +export(layer_tech_rep_dots) +export(list_besthr_styles) export(make_data) export(make_data2) export(make_data3) +export(plot_bootstrap_raincloud) +export(plot_raincloud) +export(save_besthr) +export(scale_color_besthr) +export(scale_colour_besthr) +export(scale_fill_besthr) +export(theme_besthr) +export(update_config) importFrom(ggplot2,after_stat) importFrom(magrittr,"%>%") importFrom(rlang,":=") diff --git a/NEWS.md b/NEWS.md new file mode 100644 index 0000000..d4ccda3 --- /dev/null +++ b/NEWS.md @@ -0,0 +1,26 @@ +# besthr 0.4.0 + +## New Features +* Added new plot types and visualization options +* Enhanced plotting capabilities with additional customization + +## Infrastructure +* Added comprehensive CI/CD workflows for automated testing across R versions and platforms +* Implemented automated pkgdown documentation site with GitHub Pages deployment +* Added code quality checks, linting, and test coverage reporting +* Set up automated CRAN release workflow + +## Bug Fixes and Improvements +* Fixed README generation workflow with correct figure paths +* Updated documentation and vignettes +* Improved package development workflow and testing infrastructure + +## Documentation +* Added comprehensive development guide (CLAUDE.md) +* Enhanced README with proper badges and figure display +* Automated documentation site deployment + +--- + +# besthr 0.3.2 +* Previous CRAN release \ No newline at end of file diff --git a/R/export.R b/R/export.R new file mode 100644 index 0000000..ff47cf0 --- /dev/null +++ b/R/export.R @@ -0,0 +1,58 @@ +#' Save besthr plot to file +#' +#' Saves a besthr visualization to a file with sensible publication defaults. +#' Supports PNG, PDF, SVG, and TIFF formats. +#' +#' @param hrest An hrest object from \code{\link{estimate}} +#' @param filename Output filename. Format is detected from extension. +#' @param type Plot type: "default" (two-panel) or "raincloud" +#' @param width Plot width in inches (default 8) +#' @param height Plot height in inches (default 6) +#' @param dpi Resolution in dots per inch (default 300) +#' @param ... Additional arguments passed to the plot function (e.g., theme, colors) +#' +#' @return The filename (invisibly) +#' +#' @export +#' +#' @examples +#' \dontrun{ +#' d <- make_data() +#' hr <- estimate(d, score, group) +#' save_besthr(hr, "figure1.png") +#' save_besthr(hr, "figure1.pdf", width = 10, height = 8) +#' save_besthr(hr, "figure1.png", type = "raincloud") +#' } +#' +save_besthr <- function(hrest, filename, type = "default", + width = 8, height = 6, dpi = 300, ...) { + if (!inherits(hrest, "hrest")) { + stop("hrest must be an hrest object from estimate()") + } + + # Detect format from extension + ext <- tolower(tools::file_ext(filename)) + valid_formats <- c("png", "pdf", "svg", "tiff", "tif", "jpeg", "jpg") + if (!ext %in% valid_formats) { + stop("Unsupported format: ", ext, + ". Supported formats: ", paste(valid_formats, collapse = ", ")) + } + + # Generate the plot + p <- switch(type, + default = plot(hrest, ...), + raincloud = plot_raincloud(hrest, ...), + stop("Unknown plot type: ", type, ". Choose 'default' or 'raincloud'.") + ) + + # Save using ggplot2::ggsave + ggplot2::ggsave( + filename = filename, + plot = p, + width = width, + height = height, + dpi = dpi + ) + + invisible(filename) +} diff --git a/R/functions.R b/R/functions.R index f849e57..e2cf613 100644 --- a/R/functions.R +++ b/R/functions.R @@ -362,130 +362,3 @@ print.hrest <- function(x, ...){ } -#' dot plot of ranked data without technical replicates -#' -#' \code{dot_plot} returns a ggplot object of ranked data with group on the -#' x-axis and rank on the y-axis. Point size indicates the number of -#' observations seen at that point. A per group horizontal line shows the group -#' ranked mean -#' -#' @param hrest the hrest object from \code{estimate} -#' @param group_col quoted group column name -#' @keywords internal -#' importFrom rlang .data -dot_plot <- function(hrest, group_col){ - hrest$ranked_data %>% - dplyr::group_by(!!group_col, rank) %>% - dplyr::summarise(count = dplyr::n() ) %>% - ggplot2::ggplot() + - ggplot2::aes(!!group_col, rank) + - ggplot2::geom_point(ggplot2::aes(size = .data$count, colour = !!group_col, - fill = !!group_col)) + - ggplot2::geom_hline(ggplot2::aes(yintercept = mean, colour = !!group_col), - data = hrest$group_means, linetype = 3, size = 1) + - ggplot2::theme_minimal() - -} - -#' dot plot of score data with technical replicates -#' -#' \code{tech_rep_dot_plot} returns a ggplot object of score data with group on -#' technical replicate on the x-axis, score on the y-axis with point size -#' representing the number of observations at that point. Facets represent -#' individual groups -#' @param hrest the hrest object from \code{estimate} -#' @param score_col quoted score column name -#' @param group_col quoted group column name -#' @param tech_rep_col quoted tech replicate column name -#' @keywords internal -#' ImportFrom rlang .data -tech_rep_dot_plot <- function(hrest, score_col, group_col, tech_rep_col){ - - hrest$original_data %>% factorise_cols( list(group_col, tech_rep_col)) %>% - dplyr::group_by(!!group_col, !!tech_rep_col, !!score_col) %>% - dplyr::summarise(count = dplyr::n() ) %>% - ggplot2::ggplot() + - ggplot2::aes( !!tech_rep_col, !!score_col ) + - ggplot2::geom_point( - ggplot2::aes( - size = .data$count, - colour = !!group_col, - fill = !!group_col - ) - ) + - ggplot2::theme_minimal() + - ggplot2::facet_wrap( ggplot2::vars(!!group_col), strip.position = "bottom", nrow = 1) + - ggplot2::theme(strip.background = ggplot2::element_blank(), - strip.placement = "outside") - -} - -#' plots the \code{hrest} object -#' -#' returns a ggplot object representing the hrest object from -#' \code{\link{estimate}}. The content of left panel varies according to the -#' value of the \code{which} parameter. If \code{which = "rank_simulation"} is -#' used a plot of rank score values will be plotted in the left panel. In this -#' case technical replicates will be averaged if provided. If -#' \code{which = "just_data" } a plot of scores only is created and technical -#' replicates are displayed as is. In each case, the right hand panel shows the -#' rank bootstrap distribution and confidence interval boundaries for all non- -#' control groups. -#' -#' @param x the \code{hrest} object from \code{\link{estimate}} -#' @param which the type of left hand panel to create. Either "rank_simulation" -#' or "just_data" -#' @param ... Other parameters -#' @examples -#' -#' d1 <- make_data() -#' hr_est <- estimate(d1, score, group) -#' plot(hr_est) -#' -#' @export -#' @return ggplot object -#' @importFrom stats quantile -#' @importFrom ggplot2 after_stat -plot.hrest <- function(x, ..., which = "rank_simulation"){ - hrest <- x - group_col <- names(hrest$group_n)[ names(hrest$group_n) != "n" ][[1]] - group_col <- rlang::sym(group_col) - - a <- NULL - if (length(hrest$column_info) == 3 && which == "just_data" ){ - quo_score_col <- hrest$column_info[[1]] - quo_group_col <- hrest$column_info[[2]] - quo_tech_rep_col <- hrest$column_info[[3]] - - a <- tech_rep_dot_plot(hrest, quo_score_col, quo_group_col, - quo_tech_rep_col) - - } - else { - quo_group_col <- hrest$column_info[[2]] - a <- dot_plot(hrest, quo_group_col) - } - - - - - b <- hrest$bootstraps %>% - ggplot2::ggplot() + ggplot2::aes(mean, !!group_col, - fill = factor(after_stat(quantile))) + - ggplot2::xlim(min(hrest$ranked_data$rank), max(hrest$ranked_data$rank)) + - ggridges::stat_density_ridges(geom = "density_ridges_gradient", - calc_ecdf = TRUE, - quantiles = c(hrest$low, hrest$high) ) + - ggplot2::scale_fill_manual(values = - c("#0000FFA0", "#A0A0A0A0", "#FF0000A0"), - name = "percentile", labels=c(paste0("<", hrest$low), paste0(hrest$low, "-", hrest$high), paste0(">", hrest$high)), - guide = ggplot2::guide_legend(reverse=TRUE) ) + - ggplot2::coord_flip() + ggplot2::theme_minimal() - - p <- patchwork::wrap_plots(a,b) + patchwork::guide_area() + - patchwork::plot_layout( guides="collect") + - ggplot2::theme(legend.position = "bottom") - - return(p) - -} diff --git a/R/plot-config.R b/R/plot-config.R new file mode 100644 index 0000000..e5c6f62 --- /dev/null +++ b/R/plot-config.R @@ -0,0 +1,153 @@ +#' Create a besthr plot configuration +#' +#' Creates a configuration object that controls the appearance and behavior +#' of besthr plots. All parameters have defaults that reproduce the original +#' besthr appearance for backward compatibility. +#' +#' @param panel_widths Numeric vector of relative panel widths for patchwork layout. +#' @param panel_spacing A grid unit specifying spacing between panels. +#' @param y_limits Numeric vector of length 2 for y-axis limits, or NULL for auto. +#' @param y_expand Numeric giving proportional expansion of y-axis limits. +#' @param point_size_range Numeric vector of length 2 for min/max point sizes. +#' @param point_alpha Numeric between 0 and 1 for point transparency. +#' @param mean_line_type Line type for mean indicator lines. +#' @param mean_line_width Line width for mean indicator lines. +#' @param density_alpha Numeric between 0 and 1 for density plot transparency. +#' @param density_style Character: "points" (default, jittered bootstrap points), +#' "gradient" (density with CI shading), or "solid" (single color density). +#' @param theme_style Character: "classic" or "modern". +#' @param color_palette Character: "default", "okabe_ito", or "viridis". +#' +#' @return An object of class "besthr_plot_config" containing all plot settings. +#' +#' @export +#' +#' @examples +#' cfg <- besthr_plot_config() +#' cfg <- besthr_plot_config(panel_widths = c(2, 1), theme_style = "modern") +#' +besthr_plot_config <- function( + panel_widths = c(1, 1), + panel_spacing = grid::unit(0.5, "cm"), + y_limits = NULL, + y_expand = 0.05, + point_size_range = c(2, 8), + point_alpha = 0.8, + mean_line_type = 3, + mean_line_width = 1, + density_alpha = 0.7, + density_style = "points", + theme_style = "modern", + color_palette = "okabe_ito" +) { + if (!is.numeric(panel_widths) || length(panel_widths) < 1) { + stop("panel_widths must be a numeric vector") + } + + if (!is.null(y_limits) && (!is.numeric(y_limits) || length(y_limits) != 2)) { + stop("y_limits must be NULL or a numeric vector of length 2") + } + + if (!is.numeric(y_expand) || length(y_expand) != 1 || y_expand < 0) { + stop("y_expand must be a non-negative number") + } + + if (!is.numeric(point_size_range) || length(point_size_range) != 2) { + stop("point_size_range must be a numeric vector of length 2") + } + + if (!is.numeric(point_alpha) || point_alpha < 0 || point_alpha > 1) { + stop("point_alpha must be a number between 0 and 1") + } + + if (!is.numeric(density_alpha) || density_alpha < 0 || density_alpha > 1) { + stop("density_alpha must be a number between 0 and 1") + } + + if (!density_style %in% c("gradient", "solid", "points")) { + stop("density_style must be 'gradient', 'solid', or 'points'") + } + + if (!theme_style %in% c("classic", "modern")) { + stop("theme_style must be 'classic' or 'modern'") + } + + if (!color_palette %in% c("default", "okabe_ito", "viridis")) { + stop("color_palette must be 'default', 'okabe_ito', or 'viridis'") + } + + structure( + list( + panel_widths = panel_widths, + panel_spacing = panel_spacing, + y_limits = y_limits, + y_expand = y_expand, + point_size_range = point_size_range, + point_alpha = point_alpha, + mean_line_type = mean_line_type, + mean_line_width = mean_line_width, + density_alpha = density_alpha, + density_style = density_style, + theme_style = theme_style, + color_palette = color_palette + ), + class = "besthr_plot_config" + ) +} + +#' Print method for besthr_plot_config +#' +#' @param x A besthr_plot_config object +#' @param ... Additional arguments (ignored) +#' +#' @return Invisibly returns x +#' @export +#' +print.besthr_plot_config <- function(x, ...) { + cat("besthr plot configuration:\n") + cat(" Panel widths:", paste(x$panel_widths, collapse = ", "), "\n") + cat(" Y-axis limits:", if (is.null(x$y_limits)) "auto" else paste(x$y_limits, collapse = " - "), "\n") + cat(" Y-axis expand:", x$y_expand, "\n") + cat(" Point size range:", paste(x$point_size_range, collapse = " - "), "\n") + cat(" Point alpha:", x$point_alpha, "\n") + cat(" Mean line type:", x$mean_line_type, "\n") + cat(" Mean line width:", x$mean_line_width, "\n") + cat(" Density alpha:", x$density_alpha, "\n") + cat(" Density style:", x$density_style, "\n") + cat(" Theme:", x$theme_style, "\n") + cat(" Colors:", x$color_palette, "\n") + invisible(x) +} + +#' Update a besthr plot configuration +#' +#' Creates a new configuration by updating specific fields of an existing one. +#' +#' @param config An existing besthr_plot_config object +#' @param ... Named arguments to update +#' +#' @return A new besthr_plot_config object +#' @export +#' +#' @examples +#' cfg <- besthr_plot_config() +#' cfg2 <- update_config(cfg, theme_style = "modern", panel_widths = c(2, 1)) +#' +update_config <- function(config, ...) { + if (!inherits(config, "besthr_plot_config")) { + stop("config must be a besthr_plot_config object") + } + + updates <- list(...) + valid_names <- names(config) + + for (name in names(updates)) { + if (!name %in% valid_names) { + stop("Unknown config parameter: ", name) + } + } + + args <- as.list(config) + args[names(updates)] <- updates + do.call(besthr_plot_config, args) +} diff --git a/R/plot-hrest.R b/R/plot-hrest.R new file mode 100644 index 0000000..8a78d7e --- /dev/null +++ b/R/plot-hrest.R @@ -0,0 +1,205 @@ +#' plots the \code{hrest} object +#' +#' returns a ggplot object representing the hrest object from +#' \code{\link{estimate}}. The content of left panel varies according to the +#' value of the \code{which} parameter. If \code{which = "rank_simulation"} is +#' used a plot of rank score values will be plotted in the left panel. In this +#' case technical replicates will be averaged if provided. If +#' \code{which = "just_data" } a plot of scores only is created and technical +#' replicates are displayed as is. In each case, the right hand panel shows the +#' rank bootstrap distribution and confidence interval boundaries for all non- +#' control groups. +#' +#' @param x the \code{hrest} object from \code{\link{estimate}} +#' @param which the type of left hand panel to create. Either "rank_simulation" +#' or "just_data" +#' @param theme the visual theme to use. Either "modern" (default, cleaner +#' contemporary style) or "classic" (original besthr appearance) +#' @param colors the color palette to use. Either "okabe_ito" (default, +#' colorblind-safe), "default" (original colors), or "viridis" +#' @param config an optional besthr_plot_config object for advanced customization. +#' If provided, theme and colors parameters are ignored. +#' @param show_significance Logical, whether to show significance stars on groups +#' where CI doesn't overlap control (default FALSE) +#' @param show_effect_size Logical, whether to show effect size annotation (default FALSE) +#' @param ... Other parameters (ignored) +#' +#' @examples +#' +#' d1 <- make_data() +#' hr_est <- estimate(d1, score, group) +#' plot(hr_est) +#' +#' # Use modern theme with colorblind-safe palette +#' plot(hr_est, theme = "modern", colors = "okabe_ito") +#' +#' # Advanced configuration +#' cfg <- besthr_plot_config( +#' panel_widths = c(2, 1), +#' point_size_range = c(3, 10) +#' ) +#' plot(hr_est, config = cfg) +#' +#' @export +#' @return ggplot object +#' @importFrom stats quantile +#' @importFrom ggplot2 after_stat +plot.hrest <- function(x, ..., which = "rank_simulation", + theme = "modern", colors = "okabe_ito", + config = NULL, + show_significance = FALSE, + show_effect_size = FALSE) { + hrest <- x + + # Build config from parameters or use provided config + if (is.null(config)) { + config <- besthr_plot_config( + theme_style = theme, + color_palette = colors + ) + } + + # Create unified data view (computes aligned limits) + data_view <- besthr_data_view(hrest, config) + + # Message about CI bounds + low_pct <- data_view$quantiles["low"] * 100 + high_pct <- data_view$quantiles["high"] * 100 + message(sprintf("Confidence interval: %.1f%% - %.1f%%", low_pct, high_pct)) + + # Build panels + p1 <- build_observation_panel(data_view, config, which) + p2 <- build_bootstrap_panel(data_view, config) + + # Add significance annotations if requested + if (show_significance) { + sig <- compute_significance(hrest) + sig_data <- sig[!is.na(sig$significant) & sig$stars != "", ] + + if (nrow(sig_data) > 0) { + # Get y positions from group means for significant groups + group_col_name <- data_view$group_col_name + sig_data$y_pos <- sapply(sig_data$group, function(g) { + data_view$group_means$mean[data_view$group_means[[group_col_name]] == g] + }) + + p1 <- p1 + ggplot2::geom_text( + data = sig_data, + mapping = ggplot2::aes( + x = group, + y = y_pos, + label = stars + ), + vjust = -0.5, + hjust = 0.5, + size = 5, + inherit.aes = FALSE + ) + } + } + + # Add effect size annotation if requested + if (show_effect_size) { + effect <- compute_effect_size(hrest) + effect_data <- effect[!is.na(effect$effect), ] + + if (nrow(effect_data) > 0) { + effect_text <- paste( + sapply(seq_len(nrow(effect_data)), function(i) { + sprintf("%s: %.2f [%.2f, %.2f]", + effect_data$group[i], + effect_data$effect[i], + effect_data$effect_ci_low[i], + effect_data$effect_ci_high[i]) + }), + collapse = "\n" + ) + + # Add as subtitle via patchwork annotation + p2 <- p2 + ggplot2::labs(caption = paste("Effect sizes:", effect_text)) + } + } + + # Compose with smart alignment + compose_besthr_panels(list(p1, p2), config) +} + +#' dot plot of ranked data without technical replicates +#' +#' \code{dot_plot} returns a ggplot object of ranked data with group on the +#' x-axis and rank on the y-axis. Point size indicates the number of +#' observations seen at that point. A per group horizontal line shows the group +#' ranked mean +#' +#' @param hrest the hrest object from \code{estimate} +#' @param group_col quoted group column name +#' @param theme_style character specifying the theme style +#' @param color_palette character specifying the color palette +#' @keywords internal +#' importFrom rlang .data +dot_plot <- function(hrest, group_col, theme_style = "modern", + color_palette = "okabe_ito") { + p <- hrest$ranked_data %>% + dplyr::group_by(!!group_col, rank) %>% + dplyr::summarise(count = dplyr::n(), .groups = "drop") %>% + ggplot2::ggplot() + + ggplot2::aes(!!group_col, rank) + + ggplot2::geom_point(ggplot2::aes(size = .data$count, colour = !!group_col, + fill = !!group_col)) + + ggplot2::geom_hline(ggplot2::aes(yintercept = mean, colour = !!group_col), + data = hrest$group_means, linetype = 3, linewidth = 1) + + # Apply theme + p <- p + theme_besthr(theme_style) + + # Apply color palette if not default (to preserve backward compatibility) + if (color_palette != "default") { + p <- p + scale_color_besthr(color_palette) + scale_fill_besthr(color_palette) + } + + p +} + +#' dot plot of score data with technical replicates +#' +#' \code{tech_rep_dot_plot} returns a ggplot object of score data with group on +#' technical replicate on the x-axis, score on the y-axis with point size +#' representing the number of observations at that point. Facets represent +#' individual groups +#' @param hrest the hrest object from \code{estimate} +#' @param score_col quoted score column name +#' @param group_col quoted group column name +#' @param tech_rep_col quoted tech replicate column name +#' @param theme_style character specifying the theme style +#' @param color_palette character specifying the color palette +#' @keywords internal +#' ImportFrom rlang .data +tech_rep_dot_plot <- function(hrest, score_col, group_col, tech_rep_col, + theme_style = "modern", color_palette = "okabe_ito") { + + p <- hrest$original_data %>% factorise_cols(list(group_col, tech_rep_col)) %>% + dplyr::group_by(!!group_col, !!tech_rep_col, !!score_col) %>% + dplyr::summarise(count = dplyr::n(), .groups = "drop") %>% + ggplot2::ggplot() + + ggplot2::aes(!!tech_rep_col, !!score_col) + + ggplot2::geom_point( + ggplot2::aes( + size = .data$count, + colour = !!group_col, + fill = !!group_col + ) + ) + + ggplot2::facet_wrap(ggplot2::vars(!!group_col), strip.position = "bottom", nrow = 1) + + # Apply theme + p <- p + theme_besthr(theme_style) + + ggplot2::theme(strip.background = ggplot2::element_blank(), + strip.placement = "outside") + + # Apply color palette if not default (to preserve backward compatibility) + if (color_palette != "default") { + p <- p + scale_color_besthr(color_palette) + scale_fill_besthr(color_palette) + } + + p +} diff --git a/R/plot-layers.R b/R/plot-layers.R new file mode 100644 index 0000000..e6402a1 --- /dev/null +++ b/R/plot-layers.R @@ -0,0 +1,261 @@ +#' Create a unified data view for besthr plotting +#' +#' Extracts and organizes data from an hrest object for plotting. Computes +#' unified axis limits that ensure alignment between observation and bootstrap +#' panels. +#' +#' @param hrest An hrest object from \code{\link{estimate}} +#' @param config A besthr_plot_config object (optional). If NULL, uses defaults. +#' +#' @return An object of class "besthr_data_view" containing ranked data, +#' original data, bootstrap samples, group means, confidence intervals, +#' group sample sizes, unified rank limits, group column symbol, column info, +#' control group name, and quantile values. +#' +#' @export +#' +#' @examples +#' d <- make_data() +#' hr <- estimate(d, score, group) +#' dv <- besthr_data_view(hr) +#' +besthr_data_view <- function(hrest, config = NULL) { + if (!inherits(hrest, "hrest")) { + stop("hrest must be an hrest object from estimate()") + } + + if (is.null(config)) { + config <- besthr_plot_config() + } + + # Extract group column name + group_col_name <- names(hrest$group_n)[names(hrest$group_n) != "n"][[1]] + group_col <- rlang::sym(group_col_name) + + # Compute unified rank limits from ranked data and bootstrap means only + # (original_data scores may have different range than ranks) + all_ranks <- c( + hrest$ranked_data$rank, + hrest$bootstraps$mean + ) + rank_min <- min(all_ranks, na.rm = TRUE) + rank_max <- max(all_ranks, na.rm = TRUE) + + # Apply expansion (minimal padding to avoid excessive whitespace) + if (!is.null(config$y_limits)) { + rank_limits <- config$y_limits + } else { + padding <- (rank_max - rank_min) * config$y_expand + rank_limits <- c(max(0, rank_min - padding), rank_max + padding) + } + + structure( + list( + ranked = hrest$ranked_data, + original = hrest$original_data, + bootstrap = hrest$bootstraps, + group_means = hrest$group_means, + ci = hrest$ci, + group_n = hrest$group_n, + rank_limits = rank_limits, + group_col = group_col, + group_col_name = group_col_name, + column_info = hrest$column_info, + control = hrest$control, + quantiles = c(low = hrest$low, high = hrest$high) + ), + class = "besthr_data_view" + ) +} + +#' Print method for besthr_data_view +#' +#' @param x A besthr_data_view object +#' @param ... Additional arguments (ignored) +#' +#' @return Invisibly returns x +#' @export +#' +print.besthr_data_view <- function(x, ...) { + cat("besthr data view:\n") + cat(" Groups:", nrow(x$group_n), "\n") + cat(" Observations:", nrow(x$ranked), "\n") + cat(" Bootstrap iterations:", nrow(x$bootstrap) / (nrow(x$group_n) - 1), "\n") + cat(" Rank limits:", paste(round(x$rank_limits, 2), collapse = " - "), "\n") + cat(" Control:", x$control, "\n") + invisible(x) +} + +#' Create ranked dots layer +#' +#' Creates a ggplot2 layer showing ranked observations as points, where point +#' size indicates the count of observations at each rank/group combination. +#' +#' @param data_view A besthr_data_view object +#' @param config A besthr_plot_config object +#' +#' @return A list of ggplot2 layers +#' +#' @export +#' +#' @examples +#' \donttest{ +#' d <- make_data() +#' hr <- estimate(d, score, group) +#' dv <- besthr_data_view(hr) +#' cfg <- besthr_plot_config() +#' # layer_ranked_dots returns layers to add to a ggplot +#' } +#' +layer_ranked_dots <- function(data_view, config) { + group_col <- data_view$group_col + + # Compute count summary + dot_data <- data_view$ranked %>% + dplyr::group_by(!!group_col, rank) %>% + dplyr::summarise(count = dplyr::n(), .groups = "drop") + + list( + ggplot2::geom_point( + data = dot_data, + mapping = ggplot2::aes( + x = !!group_col, + y = rank, + size = count, + colour = !!group_col, + fill = !!group_col + ), + alpha = config$point_alpha + ), + ggplot2::scale_size_continuous(range = config$point_size_range) + ) +} + +#' Create group mean lines layer +#' +#' Creates a ggplot2 layer showing horizontal lines at group mean ranks. +#' +#' @param data_view A besthr_data_view object +#' @param config A besthr_plot_config object +#' +#' @return A ggplot2 layer +#' +#' @export +#' +#' @examples +#' \donttest{ +#' d <- make_data() +#' hr <- estimate(d, score, group) +#' dv <- besthr_data_view(hr) +#' cfg <- besthr_plot_config() +#' # layer_group_means returns a ggplot layer +#' } +#' +layer_group_means <- function(data_view, config) { + group_col <- data_view$group_col + + ggplot2::geom_hline( + data = data_view$group_means, + mapping = ggplot2::aes(yintercept = mean, colour = !!group_col), + linetype = config$mean_line_type, + linewidth = config$mean_line_width + ) +} + +#' Create bootstrap density layer +#' +#' Creates a ggplot2 layer showing ridge density plots of bootstrap distributions +#' with confidence interval shading. +#' +#' @param data_view A besthr_data_view object +#' @param config A besthr_plot_config object +#' +#' @return A list of ggplot2 layers +#' +#' @export +#' @importFrom ggplot2 after_stat +#' +#' @examples +#' \donttest{ +#' d <- make_data() +#' hr <- estimate(d, score, group) +#' dv <- besthr_data_view(hr) +#' cfg <- besthr_plot_config() +#' # layer_bootstrap_density returns layers to add to a ggplot +#' } +#' +layer_bootstrap_density <- function(data_view, config) { + group_col <- data_view$group_col + ci_colors <- derive_ci_colors(config$color_palette, config$theme_style) + + low <- data_view$quantiles["low"] + high <- data_view$quantiles["high"] + + list( + ggplot2::aes(x = mean, y = !!group_col, fill = factor(after_stat(quantile))), + ggridges::stat_density_ridges( + geom = "density_ridges_gradient", + calc_ecdf = TRUE, + quantiles = c(low, high), + alpha = config$density_alpha + ), + ggplot2::scale_fill_manual( + values = ci_colors, + name = "percentile", + labels = c( + paste0("<", low), + paste0(low, "-", high), + paste0(">", high) + ), + guide = ggplot2::guide_legend(reverse = TRUE) + ) + ) +} + +#' Create technical replicate dots layer +#' +#' Creates a ggplot2 layer showing raw score observations with technical +#' replicates displayed separately. Points are sized by observation count. +#' +#' @param data_view A besthr_data_view object +#' @param config A besthr_plot_config object +#' +#' @return A list of ggplot2 layers +#' +#' @export +#' +layer_tech_rep_dots <- function(data_view, config) { + if (length(data_view$column_info) < 3) { + stop("Technical replicate layer requires data with technical replicates") + } + + score_col <- data_view$column_info[[1]] + group_col <- data_view$column_info[[2]] + tech_rep_col <- data_view$column_info[[3]] + + # Compute count summary + dot_data <- data_view$original %>% + factorise_cols(list(group_col, tech_rep_col)) %>% + dplyr::group_by(!!group_col, !!tech_rep_col, !!score_col) %>% + dplyr::summarise(count = dplyr::n(), .groups = "drop") + + list( + ggplot2::geom_point( + data = dot_data, + mapping = ggplot2::aes( + x = !!tech_rep_col, + y = !!score_col, + size = count, + colour = !!group_col, + fill = !!group_col + ), + alpha = config$point_alpha + ), + ggplot2::scale_size_continuous(range = config$point_size_range), + ggplot2::facet_wrap( + ggplot2::vars(!!group_col), + strip.position = "bottom", + nrow = 1 + ) + ) +} diff --git a/R/plot-panels.R b/R/plot-panels.R new file mode 100644 index 0000000..15430fa --- /dev/null +++ b/R/plot-panels.R @@ -0,0 +1,374 @@ +#' Build the observation panel +#' +#' Creates a ggplot showing either ranked observations (averaged tech reps) or +#' raw scores with technical replicates displayed. +#' +#' @param data_view A besthr_data_view object +#' @param config A besthr_plot_config object +#' @param which Character specifying panel type: "rank_simulation" for averaged +#' ranked data, or "just_data" for raw scores with tech reps. +#' +#' @return A ggplot object +#' +#' @export +#' +#' @examples +#' d <- make_data() +#' hr <- estimate(d, score, group) +#' dv <- besthr_data_view(hr) +#' cfg <- besthr_plot_config() +#' +#' build_observation_panel(dv, cfg, "rank_simulation") +#' +build_observation_panel <- function(data_view, config, which = "rank_simulation") { + if (!which %in% c("rank_simulation", "just_data")) { + stop("which must be 'rank_simulation' or 'just_data'") + } + + group_col <- data_view$group_col + + # Determine if we have tech reps and user wants just_data + has_tech_reps <- length(data_view$column_info) == 3 + + if (has_tech_reps && which == "just_data") { + # Tech rep dot plot (raw scores) + p <- build_tech_rep_panel(data_view, config) + } else { + # Standard ranked dot plot + p <- build_ranked_panel(data_view, config) + } + + p +} + +#' Build the ranked observation panel +#' +#' Internal function to build the panel showing ranked observations. +#' +#' @param data_view A besthr_data_view object +#' @param config A besthr_plot_config object +#' +#' @return A ggplot object +#' +#' @keywords internal +#' +build_ranked_panel <- function(data_view, config) { + group_col <- data_view$group_col + + # Compute count summary for dot sizes + dot_data <- data_view$ranked %>% + dplyr::group_by(!!group_col, rank) %>% + dplyr::summarise(count = dplyr::n(), .groups = "drop") + + p <- ggplot2::ggplot(dot_data) + + ggplot2::aes(x = !!group_col, y = rank) + + ggplot2::geom_point( + ggplot2::aes(size = count, colour = !!group_col, fill = !!group_col), + alpha = config$point_alpha + ) + + ggplot2::geom_hline( + data = data_view$group_means, + mapping = ggplot2::aes(yintercept = mean, colour = !!group_col), + linetype = config$mean_line_type, + linewidth = config$mean_line_width + ) + + ggplot2::scale_size_continuous( + range = config$point_size_range, + guide = ggplot2::guide_legend( + direction = "horizontal", + title.position = "left", + nrow = 1 + ) + ) + + ggplot2::ylim(data_view$rank_limits) + + # Apply theme + p <- p + theme_besthr(config$theme_style) + + # Apply color palette with drop = FALSE for consistent colors + if (config$color_palette != "default") { + p <- p + + scale_color_besthr(config$color_palette, drop = FALSE) + + scale_fill_besthr(config$color_palette, drop = FALSE) + } else { + p <- p + + ggplot2::scale_colour_discrete(drop = FALSE) + + ggplot2::scale_fill_discrete(drop = FALSE) + } + + p +} + +#' Build the technical replicate panel +#' +#' Internal function to build the panel showing raw scores with tech reps. +#' +#' @param data_view A besthr_data_view object +#' @param config A besthr_plot_config object +#' +#' @return A ggplot object +#' +#' @keywords internal +#' +build_tech_rep_panel <- function(data_view, config) { + score_col <- data_view$column_info[[1]] + group_col <- data_view$column_info[[2]] + tech_rep_col <- data_view$column_info[[3]] + + # Compute count summary for dot sizes + dot_data <- data_view$original %>% + factorise_cols(list(group_col, tech_rep_col)) %>% + dplyr::group_by(!!group_col, !!tech_rep_col, !!score_col) %>% + dplyr::summarise(count = dplyr::n(), .groups = "drop") + + p <- ggplot2::ggplot(dot_data) + + ggplot2::aes(x = !!tech_rep_col, y = !!score_col) + + ggplot2::geom_point( + ggplot2::aes(size = count, colour = !!group_col, fill = !!group_col), + alpha = config$point_alpha + ) + + ggplot2::scale_size_continuous(range = config$point_size_range) + + ggplot2::facet_wrap( + ggplot2::vars(!!group_col), + strip.position = "bottom", + nrow = 1 + ) + + # Apply theme + p <- p + theme_besthr(config$theme_style) + + ggplot2::theme( + strip.background = ggplot2::element_blank(), + strip.placement = "outside" + ) + + # Apply color palette with drop = FALSE for consistent colors + if (config$color_palette != "default") { + p <- p + + scale_color_besthr(config$color_palette, drop = FALSE) + + scale_fill_besthr(config$color_palette, drop = FALSE) + } else { + p <- p + + ggplot2::scale_colour_discrete(drop = FALSE) + + ggplot2::scale_fill_discrete(drop = FALSE) + } + + p +} + +#' Build the bootstrap distribution panel +#' +#' Creates a ggplot showing ridge density plots of bootstrap distributions with +#' confidence interval shading. +#' +#' @param data_view A besthr_data_view object +#' @param config A besthr_plot_config object +#' +#' @return A ggplot object +#' +#' @export +#' @importFrom ggplot2 after_stat +#' +#' @examples +#' d <- make_data() +#' hr <- estimate(d, score, group) +#' dv <- besthr_data_view(hr) +#' cfg <- besthr_plot_config() +#' +#' build_bootstrap_panel(dv, cfg) +#' +build_bootstrap_panel <- function(data_view, config) { + group_col <- data_view$group_col + ci_colors <- derive_ci_colors(config$color_palette, config$theme_style) + + low <- data_view$quantiles["low"] + high <- data_view$quantiles["high"] + + + # Get consistent factor levels from ranked data to ensure color matching + group_levels <- unique(as.character(data_view$ranked[[data_view$group_col_name]])) + + # Apply factor levels to bootstrap and ci data for color consistency + bootstrap_data <- data_view$bootstrap + bootstrap_data[[data_view$group_col_name]] <- factor( + bootstrap_data[[data_view$group_col_name]], + levels = group_levels + ) + + ci_data <- data_view$ci + ci_data[[data_view$group_col_name]] <- factor( + ci_data[[data_view$group_col_name]], + levels = group_levels + ) + + if (config$density_style == "points") { + # Points style: jittered dots instead of density + p <- ggplot2::ggplot(bootstrap_data) + + ggplot2::aes(x = mean, y = !!group_col, colour = !!group_col) + + ggplot2::geom_jitter( + width = 0, + height = 0.15, + size = 0.5, + alpha = 0.3 + ) + + ggplot2::geom_pointrange( + data = ci_data, + mapping = ggplot2::aes( + x = mean, y = !!group_col, + xmin = low, xmax = high, + colour = !!group_col + ), + size = 0.6, + linewidth = 1 + ) + + ggplot2::xlim(data_view$rank_limits) + + ggplot2::coord_flip() + + theme_besthr(config$theme_style) + + ggplot2::guides(colour = "none") + + # Apply color palette with drop = FALSE to maintain color consistency + if (config$color_palette != "default") { + p <- p + scale_color_besthr(config$color_palette, drop = FALSE) + } else { + p <- p + ggplot2::scale_colour_discrete(drop = FALSE) + } + + } else if (config$density_style == "solid") { + # Solid style: single color density without CI shading + p <- ggplot2::ggplot(bootstrap_data) + + ggplot2::aes(x = mean, y = !!group_col, fill = !!group_col) + + ggplot2::xlim(data_view$rank_limits) + + ggridges::geom_density_ridges( + alpha = config$density_alpha, + scale = 0.9 + ) + + ggplot2::geom_vline( + data = ci_data, + mapping = ggplot2::aes(xintercept = low), + linetype = 2, + alpha = 0.5 + ) + + ggplot2::geom_vline( + data = ci_data, + mapping = ggplot2::aes(xintercept = high), + linetype = 2, + alpha = 0.5 + ) + + ggplot2::coord_flip() + + theme_besthr(config$theme_style) + + ggplot2::guides(fill = "none") + + # Apply color palette with drop = FALSE to maintain color consistency + if (config$color_palette != "default") { + p <- p + scale_fill_besthr(config$color_palette, drop = FALSE) + } else { + p <- p + ggplot2::scale_fill_discrete(drop = FALSE) + } + + } else { + # Gradient style: density in group color with faded tails outside CI + # Base density at low alpha (faded tails), then overlay CI region highlight + + # Build data for fading the tails - add rectangles with white overlay + # for regions outside CI bounds + x_limits <- data_view$rank_limits + tail_alpha <- 0.5 # Semi-transparent white to fade tails + + p <- ggplot2::ggplot(bootstrap_data) + + ggplot2::aes(x = mean, y = !!group_col, fill = !!group_col) + + ggplot2::xlim(x_limits) + + # Main density in group color + ggridges::geom_density_ridges( + alpha = config$density_alpha, + scale = 0.9, + colour = NA + ) + + ggplot2::coord_flip() + + theme_besthr(config$theme_style) + + ggplot2::guides(fill = "none") + + # Add semi-transparent white overlay on the tails (outside CI) + # This creates the "faded" effect for regions outside the CI + p <- p + + # Left tail (below low CI bound) + ggplot2::geom_rect( + data = ci_data, + mapping = ggplot2::aes( + xmin = x_limits[1], + xmax = low, + ymin = as.numeric(!!group_col) - 0.45, + ymax = as.numeric(!!group_col) + 0.45 + ), + fill = "white", + alpha = tail_alpha, + inherit.aes = FALSE + ) + + # Right tail (above high CI bound) + ggplot2::geom_rect( + data = ci_data, + mapping = ggplot2::aes( + xmin = high, + xmax = x_limits[2], + ymin = as.numeric(!!group_col) - 0.45, + ymax = as.numeric(!!group_col) + 0.45 + ), + fill = "white", + alpha = tail_alpha, + inherit.aes = FALSE + ) + + # Apply color palette with drop = FALSE to maintain color consistency + if (config$color_palette != "default") { + p <- p + scale_fill_besthr(config$color_palette, drop = FALSE) + } else { + p <- p + ggplot2::scale_fill_discrete(drop = FALSE) + } + } + + p +} + +#' Compose besthr panels +#' +#' Combines observation and bootstrap panels using patchwork with proper +#' alignment and shared legends. +#' +#' @param panels A list of ggplot objects to compose +#' @param config A besthr_plot_config object +#' +#' @return A patchwork object +#' +#' @export +#' +#' @examples +#' d <- make_data() +#' hr <- estimate(d, score, group) +#' dv <- besthr_data_view(hr) +#' cfg <- besthr_plot_config() +#' +#' p1 <- build_observation_panel(dv, cfg) +#' p2 <- build_bootstrap_panel(dv, cfg) +#' compose_besthr_panels(list(p1, p2), cfg) +#' +compose_besthr_panels <- function(panels, config) { + if (length(panels) < 2) { + stop("At least two panels are required for composition") + } + + p <- patchwork::wrap_plots(panels) + + patchwork::plot_layout( + guides = "collect", + widths = config$panel_widths + ) & + ggplot2::theme( + legend.position = "bottom", + legend.direction = "horizontal", + legend.box = "horizontal", + legend.box.margin = ggplot2::margin(0, 0, 0, 0), + legend.margin = ggplot2::margin(0, 0, 0, 0), + legend.spacing.x = ggplot2::unit(0.8, "cm"), + legend.spacing.y = ggplot2::unit(0, "cm"), + legend.key.size = ggplot2::unit(0.4, "cm") + ) + + p +} diff --git a/R/plot-raincloud.R b/R/plot-raincloud.R new file mode 100644 index 0000000..007fc52 --- /dev/null +++ b/R/plot-raincloud.R @@ -0,0 +1,215 @@ +#' Raincloud plot for hrest objects +#' +#' Creates a unified raincloud visualization combining: +#' \itemize{ +#' \item Jittered raw data points +#' \item Half-violin density plots +#' \item Mean with confidence interval as pointrange +#' } +#' +#' This provides an alternative to the standard two-panel besthr plot, +#' combining all information in a single comprehensive visualization. +#' +#' @param hrest An hrest object from \code{\link{estimate}} +#' @param theme the visual theme to use. Either "modern" (default) or "classic" +#' @param colors the color palette to use. Either "okabe_ito" (default), "default", or "viridis" +#' @param config an optional besthr_plot_config object for advanced customization. +#' If provided, theme and colors parameters are ignored. +#' @param show_bootstrap Ignored (kept for backward compatibility). +#' @param jitter_width Numeric width of jitter for data points. Default 0.15. +#' @param point_size Numeric size for jittered points. Default 1.5. +#' +#' @return A ggplot object +#' +#' @export +#' +#' @examples +#' \donttest{ +#' d <- make_data() +#' hr <- estimate(d, score, group) +#' plot_raincloud(hr) +#' } +#' +plot_raincloud <- function(hrest, theme = "modern", colors = "okabe_ito", + config = NULL, show_bootstrap = TRUE, + jitter_width = 0.15, point_size = 1.5) { + if (!inherits(hrest, "hrest")) { + stop("hrest must be an hrest object from estimate()") + } + + # Build config from parameters or use provided config + if (is.null(config)) { + config <- besthr_plot_config( + theme_style = theme, + color_palette = colors + ) + } + + # Create unified data view + data_view <- besthr_data_view(hrest, config) + group_col <- data_view$group_col + + # Base plot with ranked data + p <- ggplot2::ggplot(data_view$ranked, ggplot2::aes(x = !!group_col, y = rank)) + + # Add jittered data points + p <- p + ggplot2::geom_jitter( + ggplot2::aes(colour = !!group_col), + width = jitter_width, + height = 0, + size = point_size, + alpha = config$point_alpha + ) + + # Add mean with CI as pointrange + ci_data <- data_view$ci + + p <- p + ggplot2::geom_pointrange( + data = ci_data, + mapping = ggplot2::aes( + x = !!group_col, + y = mean, + ymin = low, + ymax = high, + colour = !!group_col + ), + size = 0.8, + linewidth = 1.2, + position = ggplot2::position_nudge(x = 0.3) + ) + + # Add group mean lines + p <- p + ggplot2::geom_hline( + data = data_view$group_means, + mapping = ggplot2::aes(yintercept = mean, colour = !!group_col), + linetype = config$mean_line_type, + linewidth = config$mean_line_width * 0.7, + alpha = 0.5 + ) + + # Apply y-axis limits + p <- p + ggplot2::ylim(data_view$rank_limits) + + # Labels + p <- p + ggplot2::labs( + x = NULL, + y = "Rank" + ) + + # Apply theme + p <- p + theme_besthr(config$theme_style) + + # Apply color palette if not default + if (config$color_palette != "default") { + p <- p + + scale_color_besthr(config$color_palette) + + scale_fill_besthr(config$color_palette) + } + + # Remove redundant legend + p <- p + ggplot2::guides( + colour = "none", + fill = "none" + ) + + p +} + +#' Raincloud plot showing bootstrap distributions +#' +#' Creates a raincloud plot specifically for bootstrap distributions, showing +#' the distribution of bootstrap mean ranks with jittered points and summary +#' statistics. +#' +#' @param hrest An hrest object from \code{\link{estimate}} +#' @param theme the visual theme to use. Either "modern" (default) or "classic" +#' @param colors the color palette to use. Either "okabe_ito" (default), "default", or "viridis" +#' @param config an optional besthr_plot_config object +#' +#' @return A ggplot object +#' +#' @export +#' +#' @examples +#' \donttest{ +#' d <- make_data() +#' hr <- estimate(d, score, group, nits = 100) +#' plot_bootstrap_raincloud(hr) +#' } +#' +plot_bootstrap_raincloud <- function(hrest, theme = "modern", colors = "okabe_ito", + config = NULL) { + if (!inherits(hrest, "hrest")) { + stop("hrest must be an hrest object from estimate()") + } + + # Build config from parameters or use provided config + if (is.null(config)) { + config <- besthr_plot_config( + theme_style = theme, + color_palette = colors + ) + } + + # Create unified data view + data_view <- besthr_data_view(hrest, config) + group_col <- data_view$group_col + + # Filter bootstrap data to exclude control group + bootstrap_data <- data_view$bootstrap + + # Build plot with jittered bootstrap means + p <- ggplot2::ggplot(bootstrap_data, ggplot2::aes(x = !!group_col, y = mean)) + + # Add jittered bootstrap points + p <- p + ggplot2::geom_jitter( + ggplot2::aes(colour = !!group_col), + width = 0.15, + height = 0, + size = 0.5, + alpha = 0.3 + ) + + # Add CI as pointrange + ci_data <- data_view$ci + p <- p + ggplot2::geom_pointrange( + data = ci_data, + mapping = ggplot2::aes( + x = !!group_col, + y = mean, + ymin = low, + ymax = high, + colour = !!group_col + ), + size = 1, + linewidth = 1.5, + position = ggplot2::position_nudge(x = 0.25) + ) + + # Y-axis limits (mean rank values) + p <- p + ggplot2::ylim(data_view$rank_limits) + + # Labels + low_pct <- data_view$quantiles["low"] * 100 + high_pct <- data_view$quantiles["high"] * 100 + p <- p + ggplot2::labs( + x = NULL, + y = "Bootstrap Mean Rank", + caption = paste0(low_pct, "% - ", high_pct, "% CI") + ) + + # Apply theme + p <- p + theme_besthr(config$theme_style) + + # Apply color palette if not default + if (config$color_palette != "default") { + p <- p + + scale_color_besthr(config$color_palette) + + scale_fill_besthr(config$color_palette) + } + + # Remove redundant legend + p <- p + ggplot2::guides(fill = "none", colour = "none") + + p +} diff --git a/R/significance.R b/R/significance.R new file mode 100644 index 0000000..d9c75d5 --- /dev/null +++ b/R/significance.R @@ -0,0 +1,152 @@ +#' Compute significance from bootstrap distributions +#' +#' Determines statistical significance by checking if the bootstrap confidence +#' interval for each treatment group overlaps with the control group's mean rank. +#' +#' @param hrest An hrest object from \code{\link{estimate}} +#' +#' @return A data frame with columns: group, significant (logical), p_value, stars +#' +#' @export +#' +#' @examples +#' d <- make_data() +#' hr <- estimate(d, score, group, nits = 500) +#' compute_significance(hr) +#' +compute_significance <- function(hrest) { + if (!inherits(hrest, "hrest")) { + stop("hrest must be an hrest object from estimate()") + } + + # Get group column name + group_col_name <- names(hrest$group_n)[names(hrest$group_n) != "n"][[1]] + + # Get control mean + control_mean <- hrest$group_means$mean[hrest$group_means[[group_col_name]] == hrest$control] + + # Get all groups + all_groups <- unique(hrest$group_means[[group_col_name]]) + + # Compute significance for each group + results <- lapply(all_groups, function(g) { + if (g == hrest$control) { + return(data.frame( + group = g, + significant = NA, + p_value = NA_real_, + stars = "", + stringsAsFactors = FALSE + )) + } + + # Get bootstrap samples for this group + group_boots <- hrest$bootstraps$mean[hrest$bootstraps[[group_col_name]] == g] + + # Compute proportion of bootstrap samples that cross control mean + # This gives a two-tailed p-value approximation + n_boots <- length(group_boots) + group_mean <- mean(group_boots) + + if (group_mean > control_mean) { + # Treatment higher - count samples below control + p_value <- sum(group_boots <= control_mean) / n_boots + } else { + # Treatment lower - count samples above control + p_value <- sum(group_boots >= control_mean) / n_boots + } + + # Two-tailed + p_value <- min(p_value * 2, 1) + + # Determine significance + significant <- p_value < 0.05 + + # Assign stars + stars <- "" + if (p_value < 0.001) { + stars <- "***" + } else if (p_value < 0.01) { + stars <- "**" + } else if (p_value < 0.05) { + stars <- "*" + } + + data.frame( + group = g, + significant = significant, + p_value = p_value, + stars = stars, + stringsAsFactors = FALSE + ) + }) + + do.call(rbind, results) +} + +#' Compute effect sizes from bootstrap distributions +#' +#' Calculates the effect size (difference from control) for each treatment group +#' with bootstrap confidence intervals. +#' +#' @param hrest An hrest object from \code{\link{estimate}} +#' +#' @return A data frame with columns: group, effect, effect_ci_low, effect_ci_high +#' +#' @export +#' +#' @examples +#' d <- make_data() +#' hr <- estimate(d, score, group, nits = 500) +#' compute_effect_size(hr) +#' +compute_effect_size <- function(hrest) { + if (!inherits(hrest, "hrest")) { + stop("hrest must be an hrest object from estimate()") + } + + # Get group column name + group_col_name <- names(hrest$group_n)[names(hrest$group_n) != "n"][[1]] + + # Get control mean + control_mean <- hrest$group_means$mean[hrest$group_means[[group_col_name]] == hrest$control] + + # Get all groups + all_groups <- unique(hrest$group_means[[group_col_name]]) + + # Compute effect size for each group + results <- lapply(all_groups, function(g) { + if (g == hrest$control) { + return(data.frame( + group = g, + effect = NA_real_, + effect_ci_low = NA_real_, + effect_ci_high = NA_real_, + stringsAsFactors = FALSE + )) + } + + # Get group mean (point estimate) + group_mean <- hrest$group_means$mean[hrest$group_means[[group_col_name]] == g] + effect <- group_mean - control_mean + + # Get bootstrap samples for this group and compute effect distribution + group_boots <- hrest$bootstraps$mean[hrest$bootstraps[[group_col_name]] == g] + + # For proper effect CI, we'd need paired bootstrap of control and treatment + # Approximation: use the CI from the group bootstrap + effect_boots <- group_boots - control_mean + effect_ci_low <- stats::quantile(effect_boots, hrest$low) + effect_ci_high <- stats::quantile(effect_boots, hrest$high) + + data.frame( + group = g, + effect = effect, + effect_ci_low = as.numeric(effect_ci_low), + effect_ci_high = as.numeric(effect_ci_high), + stringsAsFactors = FALSE + ) + }) + + do.call(rbind, results) +} diff --git a/R/table.R b/R/table.R new file mode 100644 index 0000000..bf5eebb --- /dev/null +++ b/R/table.R @@ -0,0 +1,147 @@ +#' Generate a summary table from besthr results +#' +#' Creates a publication-ready summary table containing group statistics, +#' confidence intervals, and optionally effect sizes and significance. +#' +#' @param hrest An hrest object from \code{\link{estimate}} +#' @param format Output format: "tibble" (default), "markdown", "html", or "latex" +#' @param digits Number of decimal places for rounding (default 2) +#' @param include_significance Logical, whether to include significance stars (default FALSE) +#' +#' @return A tibble (if format = "tibble") or character string (other formats) +#' +#' @export +#' +#' @examples +#' d <- make_data() +#' hr <- estimate(d, score, group) +#' besthr_table(hr) +#' besthr_table(hr, format = "markdown") +#' +besthr_table <- function(hrest, format = "tibble", digits = 2, + include_significance = FALSE) { + if (!inherits(hrest, "hrest")) { + stop("hrest must be an hrest object from estimate()") + } + + valid_formats <- c("tibble", "markdown", "html", "latex") + if (!format %in% valid_formats) { + stop("format must be one of: ", paste(valid_formats, collapse = ", ")) + } + + # Get group column name +group_col_name <- names(hrest$group_n)[names(hrest$group_n) != "n"][[1]] + + # Build base table from group_n and group_means + tbl <- dplyr::left_join( + hrest$group_n, + hrest$group_means, + by = group_col_name + ) + + # Add CI for non-control groups + ci_data <- hrest$ci + names(ci_data)[names(ci_data) == "low"] <- "ci_low" + names(ci_data)[names(ci_data) == "high"] <- "ci_high" + names(ci_data)[names(ci_data) == "mean"] <- "ci_mean" + + tbl <- dplyr::left_join(tbl, ci_data[, c(group_col_name, "ci_low", "ci_high")], + by = group_col_name) + + # Rename columns for clarity + names(tbl)[names(tbl) == "mean"] <- "mean_rank" + names(tbl)[names(tbl) == group_col_name] <- "group" + + # Compute effect size (difference from control) + control_mean <- tbl$mean_rank[tbl$group == hrest$control] + tbl$effect_size <- ifelse(tbl$group == hrest$control, NA_real_, + tbl$mean_rank - control_mean) + + # Add significance if requested + if (include_significance) { + sig <- compute_significance(hrest) + tbl <- dplyr::left_join(tbl, sig[, c("group", "stars")], by = "group") + names(tbl)[names(tbl) == "stars"] <- "significance" + } + + # Round numeric columns + numeric_cols <- c("mean_rank", "ci_low", "ci_high", "effect_size") + for (col in numeric_cols) { + if (col %in% names(tbl)) { + tbl[[col]] <- round(tbl[[col]], digits) + } + } + + # Reorder columns + col_order <- c("group", "n", "mean_rank", "ci_low", "ci_high", "effect_size") + if (include_significance) col_order <- c(col_order, "significance") + tbl <- tbl[, col_order[col_order %in% names(tbl)]] + + # Return in requested format + if (format == "tibble") { + return(tibble::as_tibble(tbl)) + } + + # Convert to formatted string + format_table(tbl, format) +} + +#' Format table as string +#' +#' Internal function to convert tibble to markdown, html, or latex string. +#' +#' @param tbl A tibble to format +#' @param format The output format +#' +#' @return A character string +#' @keywords internal +#' +format_table <- function(tbl, format) { + if (format == "markdown") { + # Build markdown table + header <- paste("|", paste(names(tbl), collapse = " | "), "|") + separator <- paste("|", paste(rep("---", ncol(tbl)), collapse = " | "), "|") + + rows <- apply(tbl, 1, function(row) { + paste("|", paste(as.character(row), collapse = " | "), "|") + }) + + return(paste(c(header, separator, rows), collapse = "\n")) + } + + if (format == "html") { + # Build HTML table + header_cells <- paste0("", names(tbl), "", collapse = "") + header_row <- paste0("", header_cells, "") + + data_rows <- apply(tbl, 1, function(row) { + cells <- paste0("", as.character(row), "", collapse = "") + paste0("", cells, "") + }) + + return(paste0( + "\n\n", header_row, "\n\n\n", + paste(data_rows, collapse = "\n"), + "\n\n
" + )) + } + + if (format == "latex") { + # Build LaTeX table + col_spec <- paste(rep("l", ncol(tbl)), collapse = "") + header <- paste(names(tbl), collapse = " & ") + rows <- apply(tbl, 1, function(row) { + paste(as.character(row), collapse = " & ") + }) + + return(paste0( + "\\begin{tabular}{", col_spec, "}\n", + "\\hline\n", + header, " \\\\\n", + "\\hline\n", + paste(rows, collapse = " \\\\\n"), " \\\\\n", + "\\hline\n", + "\\end{tabular}" + )) + } +} diff --git a/R/themes.R b/R/themes.R new file mode 100644 index 0000000..fcdf79a --- /dev/null +++ b/R/themes.R @@ -0,0 +1,376 @@ +#' besthr color palettes +#' +#' Returns a color palette suitable for besthr visualizations. The default +#' palette uses Okabe-Ito colorblind-safe colors. +#' +#' @param palette Character string specifying the palette. Options are: +#' \itemize{ +#' \item "default" - Original besthr colors +#' \item "okabe_ito" - Colorblind-safe Okabe-Ito palette +#' \item "viridis" - Viridis color scale +#' } +#' @param n Number of colors to return. If NULL, returns all colors in palette. +#' +#' @return A character vector of hex color codes +#' @export +#' +#' @examples +#' besthr_palette() +#' besthr_palette("okabe_ito", 3) +#' +besthr_palette <- function(palette = "default", n = NULL) { + # For viridis, generate colors spread across the full range + if (palette == "viridis") { + if (is.null(n)) n <- 8 + # Use option "D" (viridis) and spread colors evenly across the range + return(viridisLite::viridis(n, begin = 0.1, end = 0.9)) + } + + palettes <- list( + default = c( + "#F8766D", "#00BA38", "#619CFF", "#F564E3", + "#00BFC4", "#B79F00", "#FF6C91", "#00B0F6" + ), + okabe_ito = c( + "#E69F00", "#56B4E9", "#009E73", "#F0E442", + "#0072B2", "#D55E00", "#CC79A7", "#999999" + ) + ) + + pal <- palettes[[palette]] + if (is.null(pal)) { + stop("Unknown palette: ", palette, + ". Choose from: ", paste(names(palettes), collapse = ", "), ", viridis") + } + + if (!is.null(n)) { + if (n > length(pal)) { + pal <- grDevices::colorRampPalette(pal)(n) + } else { + pal <- pal[seq_len(n)] + } + } + + pal +} + +#' besthr ggplot2 theme +#' +#' A custom theme for besthr plots. The "classic" theme matches the original +#' besthr appearance, while "modern" provides a cleaner, more contemporary look. +#' +#' @param style Character string specifying the theme style. Options are: +#' \itemize{ +#' \item "classic" - Original besthr theme (theme_minimal) +#' \item "modern" - Clean, contemporary style with refined typography +#' } +#' @param base_size Base font size (default 11) +#' @param base_family Base font family +#' +#' @return A ggplot2 theme object +#' @export +#' +#' @examples +#' library(ggplot2) +#' ggplot(mtcars, aes(mpg, wt)) + +#' geom_point() + +#' theme_besthr("modern") +#' +theme_besthr <- function(style = "classic", base_size = 11, base_family = "") { + if (style == "classic") { + return(ggplot2::theme_minimal(base_size = base_size, base_family = base_family)) + } + + if (style == "modern") { + return( + ggplot2::theme_minimal(base_size = base_size, base_family = base_family) + + ggplot2::theme( + # Typography + plot.title = ggplot2::element_text( + size = ggplot2::rel(1.2), + face = "bold", + margin = ggplot2::margin(b = 10) + ), + axis.title = ggplot2::element_text( + size = ggplot2::rel(1.0), + face = "bold" + ), + axis.text = ggplot2::element_text(size = ggplot2::rel(0.9)), + legend.title = ggplot2::element_text(face = "bold"), + + # Grid + panel.grid.minor = ggplot2::element_blank(), + panel.grid.major = ggplot2::element_line( + color = "grey90", + linewidth = 0.3 + ), + + # Legend + legend.position = "bottom", + legend.box = "horizontal", + + # Spacing + plot.margin = ggplot2::margin(15, 15, 15, 15), + + # Strip (for facets) + strip.text = ggplot2::element_text(face = "bold", size = ggplot2::rel(1.0)), + strip.background = ggplot2::element_blank() + ) + ) + } + + stop("Unknown style: ", style, ". Choose from: classic, modern") +} + +#' Discrete color scale for besthr +#' +#' A discrete color scale using besthr palettes. +#' +#' @param palette Character string specifying the palette (see \code{\link{besthr_palette}}) +#' @param ... Additional arguments passed to \code{\link[ggplot2]{discrete_scale}} +#' +#' @return A ggplot2 discrete color scale +#' @export +#' +#' @examples +#' library(ggplot2) +#' ggplot(mtcars, aes(mpg, wt, color = factor(cyl))) + +#' geom_point() + +#' scale_color_besthr("okabe_ito") +#' +scale_color_besthr <- function(palette = "default", ...) { + ggplot2::discrete_scale( + aesthetics = "colour", + palette = function(n) besthr_palette(palette, n), + ... + ) +} + +#' @rdname scale_color_besthr +#' @export +scale_colour_besthr <- scale_color_besthr + +#' Discrete fill scale for besthr +#' +#' A discrete fill scale using besthr palettes. +#' +#' @param palette Character string specifying the palette (see \code{\link{besthr_palette}}) +#' @param ... Additional arguments passed to \code{\link[ggplot2]{discrete_scale}} +#' +#' @return A ggplot2 discrete fill scale +#' @export +#' +#' @examples +#' library(ggplot2) +#' ggplot(mtcars, aes(factor(cyl), fill = factor(cyl))) + +#' geom_bar() + +#' scale_fill_besthr("okabe_ito") +#' +scale_fill_besthr <- function(palette = "default", ...) { + ggplot2::discrete_scale( + aesthetics = "fill", + palette = function(n) besthr_palette(palette, n), + ... + ) +} + +#' Confidence interval fill colors +#' +#' Returns the fill colors used for confidence interval regions in bootstrap +#' distribution plots. +#' +#' @param style Character string: "default" for original colors, "modern" for +#' updated colors +#' +#' @return A named character vector of hex colors for low, middle, high regions +#' @keywords internal +#' +ci_fill_colors <- function(style = "default") { + if (style == "default") { + return(c("#0000FFA0", "#A0A0A0A0", "#FF0000A0")) + } + + if (style == "modern") { + return(c("#2166AC99", "#F7F7F799", "#B2182B99")) + } + + + stop("Unknown style: ", style) +} + +#' Derive CI colors based on palette and theme +#' +#' Computes confidence interval fill colors that harmonize with the selected +#' color palette and theme style. This ensures visual consistency between the +#' observation panel colors and the bootstrap density shading. +#' +#' @param palette Character string specifying the color palette: "default", +#' "okabe_ito", or "viridis" +#' @param theme_style Character string specifying the theme: "classic" or "modern" +#' +#' @return A character vector of three hex colors with alpha for low, middle, +#' and high CI regions +#' +#' @export +#' +#' @examples +#' derive_ci_colors("default", "classic") +#' derive_ci_colors("okabe_ito", "modern") +#' derive_ci_colors("viridis", "classic") +#' +derive_ci_colors <- function(palette = "default", theme_style = "classic") { + # For backward compatibility, default palette uses original CI colors + if (palette == "default") { + return(ci_fill_colors(if (theme_style == "modern") "modern" else "default")) + } + + # Okabe-Ito derived colors (colorblind-safe) + if (palette == "okabe_ito") { + return(c( + "#0072B2AA", # Blue (low) + "#999999AA", # Gray (middle) + "#D55E00AA" # Vermillion (high) + )) + } + + # Viridis derived colors + if (palette == "viridis") { + # viridis returns #RRGGBBAA format, extract RGB and add our alpha + viridis_cols <- viridisLite::viridis(3, alpha = 0.67) + return(viridis_cols) + } + + # Fallback to default + ci_fill_colors("default") +} + +#' Apply besthr theme consistently +#' +#' Applies the besthr theme and color scales to a ggplot object based on +#' configuration settings. This ensures consistent theming across all plot +#' components. +#' +#' @param p A ggplot object +#' @param config A besthr_plot_config object +#' @param include_fill Logical, whether to apply fill scale (default TRUE) +#' @param include_color Logical, whether to apply color scale (default TRUE) +#' +#' @return The ggplot object with theme and scales applied +#' +#' @export +#' +#' @examples +#' library(ggplot2) +#' p <- ggplot(mtcars, aes(mpg, wt, color = factor(cyl))) + +#' geom_point() +#' cfg <- besthr_plot_config(theme_style = "modern", color_palette = "okabe_ito") +#' apply_besthr_theme(p, cfg) +#' +apply_besthr_theme <- function(p, config, include_fill = TRUE, include_color = TRUE) { + # Apply theme + p <- p + theme_besthr(config$theme_style) + + # Apply color scales if not default (to preserve backward compatibility) + if (config$color_palette != "default") { + if (include_color) { + p <- p + scale_color_besthr(config$color_palette) + } + if (include_fill) { + p <- p + scale_fill_besthr(config$color_palette) + } + } + + p +} + +#' Get a preset plot style +#' +#' Returns a pre-configured \code{besthr_plot_config} object with sensible +#' defaults for common use cases. This is the easiest way to customize +#' besthr plot appearance without understanding all the configuration options. +#' +#' @param style Character string specifying the style preset: +#' \itemize{ +#' \item "default" - Modern theme with colorblind-safe colors (recommended) +#' \item "classic" - Original besthr appearance for backward compatibility +#' \item "publication" - Clean style suitable for journal figures +#' \item "presentation" - Larger elements for slides +#' \item "density" - Uses gradient density instead of points for bootstrap +#' } +#' +#' @return A \code{besthr_plot_config} object +#' +#' @export +#' +#' @examples +#' d <- make_data() +#' hr <- estimate(d, score, group) +#' +#' # Quick styling with presets +#' plot(hr, config = besthr_style("publication")) +#' plot(hr, config = besthr_style("presentation")) +#' plot(hr, config = besthr_style("density")) +#' +#' # Same as default +#' plot(hr, config = besthr_style("default")) +#' +besthr_style <- function(style = "default") { + styles <- list( + default = besthr_plot_config( + theme_style = "modern", + color_palette = "okabe_ito" + ), + classic = besthr_plot_config( + theme_style = "classic", + color_palette = "default" + ), + publication = besthr_plot_config( + theme_style = "modern", + color_palette = "okabe_ito", + point_size_range = c(2, 6), + point_alpha = 0.9, + density_alpha = 0.6 + ), + presentation = besthr_plot_config( + theme_style = "modern", + color_palette = "okabe_ito", + point_size_range = c(4, 12), + point_alpha = 0.9, + mean_line_width = 1.5 + ), + density = besthr_plot_config( + theme_style = "modern", + color_palette = "okabe_ito", + density_style = "gradient" + ) + ) + + if (!style %in% names(styles)) { + stop("Unknown style: '", style, "'. ", + "Choose from: ", paste(names(styles), collapse = ", ")) + } + + styles[[style]] +} + +#' List available style presets +#' +#' Shows all available preset styles that can be used with \code{besthr_style()}. +#' +#' @return A character vector of style names (invisibly) +#' +#' @export +#' +#' @examples +#' list_besthr_styles() +#' +list_besthr_styles <- function() { + cat("Available besthr style presets:\n\n") + cat(" 'default' Modern theme with colorblind-safe colors (recommended)\n") + cat(" 'classic' Original besthr appearance\n") + cat(" 'publication' Clean style for journal figures\n") + cat(" 'presentation' Larger elements for slides\n") + cat(" 'density' Gradient density display for bootstrap\n") + cat("\nUsage: plot(hr, config = besthr_style('publication'))\n") + invisible(c("default", "classic", "publication", "presentation", "density")) +} diff --git a/R/utils-pipe.R b/R/utils-pipe.R index ed08ed5..79705d2 100644 --- a/R/utils-pipe.R +++ b/R/utils-pipe.R @@ -10,3 +10,14 @@ #' @usage lhs \%>\% rhs #' @return result of previous expression NULL + +# Global variable declarations for data.frame columns used in dplyr/ggplot +# These avoid R CMD check NOTEs about undefined global variables +utils::globalVariables(c( + "count", + "low", + "high", + "rank", + "mean", + "quantile" +)) diff --git a/README.Rmd b/README.Rmd new file mode 100644 index 0000000..da66953 --- /dev/null +++ b/README.Rmd @@ -0,0 +1,312 @@ +--- +title: "besthr - Generating Bootstrap Estimation Distributions of HR Data" +author: "Dan MacLean" +date: "`r format(Sys.time(), '%d %B, %Y')`" +output: + github_document: + html_preview: false +--- + +```{r setup, include=FALSE} +knitr::opts_chunk$set(fig.path = "man/figures/") +``` + + + [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.3374507.svg)](https://doi.org/10.5281/zenodo.3374507) + [![R-CMD-check](https://github.com/TeamMacLean/besthr/workflows/R-CMD-check/badge.svg)](https://github.com/TeamMacLean/besthr/actions) + [![codecov](https://codecov.io/gh/TeamMacLean/besthr/branch/develop/graph/badge.svg)](https://codecov.io/gh/TeamMacLean/besthr) + + +## Synopsis + +besthr is a package that creates plots showing scored HR experiments and plots of distribution of means of ranks of HR score from bootstrapping. + + +## Installation + +You can install from CRAN in the usual way. + +```{r, eval=FALSE} +install.packages("besthr") + +# or for the dev version +#install.packages("devtools") +devtools::install_github("TeamMacLean/besthr") +``` + +## Citation + +Please cite as + +> Dan MacLean. (2019). TeamMacLean/besthr: Initial Release (0.3.0). Zenodo. https://doi.org/10.5281/zenodo.3374507 + + +## Documentation + +Full API docs are available here https://teammaclean.github.io/besthr/ + +## Simplest Use Case - Two Groups, No Replicates + +With a data frame or similar object, use the `estimate()` function to get the bootstrap estimates of the ranked data. + +`estimate()` has a basic function call as follows: + +`estimate(data, score_column_name, group_column_name, control = control_group_name)` + +The first argument after the + +```{r, fig.width=8, fig.height=6} +library(besthr) + +hr_data_1_file <- system.file("extdata", "example-data-1.csv", package = "besthr") +hr_data_1 <- readr::read_csv(hr_data_1_file) +head(hr_data_1) + +hr_est_1 <- estimate(hr_data_1, score, group, control = "A") +hr_est_1 + +plot(hr_est_1) +``` + + +### Setting Options + +You may select the group to set as the common reference control with `control`. + +```{r, fig.width=8, fig.height=6} +estimate(hr_data_1, score, group, control = "B" ) %>% + plot() +``` + +You may select the number of iterations of the bootstrap to perform with `nits` and the quantiles for the confidence interval with `low` and `high`. + +```{r, fig.width=8, fig.height=6} +estimate(hr_data_1, score, group, control = "A", nits = 1000, low = 0.4, high = 0.6) %>% + plot() +``` + +## Extended Use Case - Technical Replicates + +You can extend the `estimate()` options to specify a third column in the data that contains technical replicate information, add the technical replicate column name after the sample column. Technical replicates are automatically merged using the `mean()` function before ranking. + +```{r, fig.width=8, fig.height=6} + +hr_data_3_file <- system.file("extdata", "example-data-3.csv", package = "besthr") +hr_data_3 <- readr::read_csv(hr_data_3_file) +head(hr_data_3) + +hr_est_3 <- estimate(hr_data_3, score, sample, rep, control = "A") + +hr_est_3 + +plot(hr_est_3) + +``` + +### Alternate Plot Options + +In the case where you have use technical replicates and want to see those plotted you can use an extra plot option `which`. Set `which` to `just_data` if you wish the left panel of the plot to show all data without ranking. This will only work if you have technical replicates. + +```{r, fig.width=8, fig.height=6} + +hr_est_3 %>% + plot(which = "just_data") +``` + +## Built-in Themes and Color Palettes + +besthr includes built-in themes and colorblind-safe color palettes that can be applied directly through the `plot()` function. + +### Theme Options + +Use the `theme` parameter to change the overall visual style: + +- `"classic"` (default) - The original besthr appearance +- `"modern"` - A cleaner, contemporary style with refined typography and grid + +```{r, fig.width=8, fig.height=6} +# Classic theme (default - same as before) +plot(hr_est_1, theme = "classic") + +# Modern theme +plot(hr_est_1, theme = "modern") +``` + +### Color Palette Options + +Use the `colors` parameter to change the color palette: + +- `"default"` - Original besthr colors +- `"okabe_ito"` - Colorblind-safe Okabe-Ito palette +- `"viridis"` - Viridis color scale + +```{r, fig.width=8, fig.height=6} +# Colorblind-safe palette +plot(hr_est_1, colors = "okabe_ito") + +# Viridis palette +plot(hr_est_1, colors = "viridis") +``` + +### Combining Theme and Colors + +You can combine both options for a fully customized look: + +```{r, fig.width=8, fig.height=6} +# Modern theme with colorblind-safe colors +plot(hr_est_1, theme = "modern", colors = "okabe_ito") +``` + +### Using besthr Palettes Directly + +The color palettes can also be used directly in your own ggplot2 code: +```{r} +# Get palette colors +besthr_palette("okabe_ito", n = 4) + +# Available palettes +besthr_palette("default", n = 3) +besthr_palette("viridis", n = 3) +``` + +## Styling Plots + +### Recommended: Use Built-in Themes and Colors + +The easiest way to style your plots is using the `theme` and `colors` parameters: + +```{r, fig.width=8, fig.height=6} +# Modern look with colorblind-safe colors (this is the default) +plot(hr_est_1, theme = "modern", colors = "okabe_ito") + +# Classic appearance +plot(hr_est_1, theme = "classic", colors = "default") + +# Viridis color scheme +plot(hr_est_1, colors = "viridis") +``` + +### Adding Titles and Annotations + +The plot object is a `patchwork` composition. You can add titles using `plot_annotation()`: + +```{r, fig.width=8, fig.height=6} +library(patchwork) + +p <- plot(hr_est_1) + +p + plot_annotation( + title = 'HR Score Analysis', + subtitle = "Control vs Treatment", + caption = 'Generated with besthr' +) +``` + +## Raincloud Plot + +For a combined view of raw data points with summary statistics, use `plot_raincloud()`: + +```{r, fig.width=8, fig.height=6} +plot_raincloud(hr_est_1) +``` + +## Significance and Effect Size Annotations + +You can add statistical annotations to your plots to highlight significant results. + +```{r} +# Create example data with 3 groups and realistic variation +set.seed(42) +d_effect <- data.frame( + score = c( + sample(1:4, 12, replace = TRUE), # Group A: low scores (control) + sample(4:8, 12, replace = TRUE), # Group B: medium-high scores + sample(6:10, 12, replace = TRUE) # Group C: high scores + ), + group = rep(c("A", "B", "C"), each = 12) +) +hr_effect <- estimate(d_effect, score, group, control = "A", nits = 1000) +``` + +### Significance Stars + +Add significance stars to groups where the bootstrap confidence interval does not overlap the control mean: + +```{r, fig.width=8, fig.height=6} +plot(hr_effect, show_significance = TRUE) +``` + +### Effect Size Annotation + +Display effect size (difference from control) with confidence intervals: + +```{r, fig.width=8, fig.height=6} +plot(hr_effect, show_effect_size = TRUE) +``` + +### Computing Statistics Directly + +You can also access the significance and effect size calculations directly: + +```{r} +# Compute significance +compute_significance(hr_est_1) + +# Compute effect sizes +compute_effect_size(hr_est_1) +``` + +## Summary Tables + +Generate publication-ready summary tables with `besthr_table()`: + +```{r} +# Default tibble format +besthr_table(hr_effect) + +# With significance stars +besthr_table(hr_effect, include_significance = TRUE) +``` + +### Export Formats + +Generate tables in various formats for publication: + +```{r} +# Markdown format +besthr_table(hr_est_1, format = "markdown") +``` + +```{r, eval=FALSE} +# HTML format +besthr_table(hr_est_1, format = "html") + +# LaTeX format +besthr_table(hr_est_1, format = "latex") +``` + +## Publication Export + +Save your plots directly to publication-quality files: + +```{r, eval=FALSE} +# Save to PNG (default 300 DPI) +save_besthr(hr_est_1, "figure1.png") + +# Save to PDF +save_besthr(hr_est_1, "figure1.pdf", width = 10, height = 8) + +# Save raincloud plot +save_besthr(hr_est_1, "raincloud.png", type = "raincloud") + +# With custom options +save_besthr(hr_est_1, "figure1.png", + theme = "modern", + colors = "okabe_ito", + width = 10, + height = 6, + dpi = 600) +``` + +Supported formats: PNG, PDF, SVG, TIFF, JPEG + diff --git a/README.md b/README.md new file mode 100644 index 0000000..4953b7d --- /dev/null +++ b/README.md @@ -0,0 +1,514 @@ +besthr - Generating Bootstrap Estimation Distributions of HR Data +================ +Dan MacLean +18 March, 2026 + + +[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.3374507.svg)](https://doi.org/10.5281/zenodo.3374507) +[![R-CMD-check](https://github.com/TeamMacLean/besthr/workflows/R-CMD-check/badge.svg)](https://github.com/TeamMacLean/besthr/actions) +[![codecov](https://codecov.io/gh/TeamMacLean/besthr/branch/develop/graph/badge.svg)](https://codecov.io/gh/TeamMacLean/besthr) + + +## Synopsis + +besthr is a package that creates plots showing scored HR experiments and +plots of distribution of means of ranks of HR score from bootstrapping. + +## Installation + +You can install from CRAN in the usual way. + +``` r +install.packages("besthr") + +# or for the dev version +#install.packages("devtools") +devtools::install_github("TeamMacLean/besthr") +``` + +## Citation + +Please cite as + +> Dan MacLean. (2019). TeamMacLean/besthr: Initial Release (0.3.0). +> Zenodo. + +## Documentation + +Full API docs are available here + +## Simplest Use Case - Two Groups, No Replicates + +With a data frame or similar object, use the `estimate()` function to +get the bootstrap estimates of the ranked data. + +`estimate()` has a basic function call as follows: + +`estimate(data, score_column_name, group_column_name, control = control_group_name)` + +The first argument after the + +``` r +library(besthr) + +hr_data_1_file <- system.file("extdata", "example-data-1.csv", package = "besthr") +hr_data_1 <- readr::read_csv(hr_data_1_file) +``` + + ## Rows: 20 Columns: 2 + ## ── Column specification ──────────────────────────────────────────────────────── + ## Delimiter: "," + ## chr (1): group + ## dbl (1): score + ## + ## ℹ Use `spec()` to retrieve the full column specification for this data. + ## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message. + +``` r +head(hr_data_1) +``` + + ## # A tibble: 6 × 2 + ## score group + ## + ## 1 10 A + ## 2 9 A + ## 3 10 A + ## 4 10 A + ## 5 8 A + ## 6 8 A + +``` r +hr_est_1 <- estimate(hr_data_1, score, group, control = "A") +hr_est_1 +``` + + ## besthr (HR Rank Score Analysis with Bootstrap Estimation) + ## ========================================================= + ## + ## Control: A + ## + ## Unpaired mean rank difference of A (14.9, n=10) minus B (6.1, n=10) + ## 8.8 + ## Confidence Intervals (0.025, 0.975) + ## 3.96875, 8.42875 + ## + ## 100 bootstrap resamples. + +``` r +plot(hr_est_1) +``` + + ## Confidence interval: 2.5% - 97.5% + +![](man/figures/unnamed-chunk-2-1.png) + +### Setting Options + +You may select the group to set as the common reference control with +`control`. + +``` r +estimate(hr_data_1, score, group, control = "B" ) %>% + plot() +``` + + ## Confidence interval: 2.5% - 97.5% + +![](man/figures/unnamed-chunk-3-1.png) + +You may select the number of iterations of the bootstrap to perform with +`nits` and the quantiles for the confidence interval with `low` and +`high`. + +``` r +estimate(hr_data_1, score, group, control = "A", nits = 1000, low = 0.4, high = 0.6) %>% + plot() +``` + + ## Confidence interval: 40.0% - 60.0% + +![](man/figures/unnamed-chunk-4-1.png) + +## Extended Use Case - Technical Replicates + +You can extend the `estimate()` options to specify a third column in the +data that contains technical replicate information, add the technical +replicate column name after the sample column. Technical replicates are +automatically merged using the `mean()` function before ranking. + +``` r +hr_data_3_file <- system.file("extdata", "example-data-3.csv", package = "besthr") +hr_data_3 <- readr::read_csv(hr_data_3_file) +``` + + ## Rows: 36 Columns: 3 + ## ── Column specification ──────────────────────────────────────────────────────── + ## Delimiter: "," + ## chr (1): sample + ## dbl (2): score, rep + ## + ## ℹ Use `spec()` to retrieve the full column specification for this data. + ## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message. + +``` r +head(hr_data_3) +``` + + ## # A tibble: 6 × 3 + ## score sample rep + ## + ## 1 8 A 1 + ## 2 9 A 1 + ## 3 8 A 1 + ## 4 10 A 1 + ## 5 8 A 2 + ## 6 8 A 2 + +``` r +hr_est_3 <- estimate(hr_data_3, score, sample, rep, control = "A") + +hr_est_3 +``` + + ## besthr (HR Rank Score Analysis with Bootstrap Estimation) + ## ========================================================= + ## + ## Control: A + ## + ## Unpaired mean rank difference of A (5, n=3) minus B (2, n=3) + ## 3 + ## Confidence Intervals (0.025, 0.975) + ## 1.15833333333333, 3 + ## + ## Unpaired mean rank difference of A (5, n=3) minus C (8, n=3) + ## -3 + ## Confidence Intervals (0.025, 0.975) + ## 7.33333333333333, 8.66666666666667 + ## + ## 100 bootstrap resamples. + +``` r +plot(hr_est_3) +``` + + ## Confidence interval: 2.5% - 97.5% + +![](man/figures/unnamed-chunk-5-1.png) + +### Alternate Plot Options + +In the case where you have use technical replicates and want to see +those plotted you can use an extra plot option `which`. Set `which` to +`just_data` if you wish the left panel of the plot to show all data +without ranking. This will only work if you have technical replicates. + +``` r +hr_est_3 %>% + plot(which = "just_data") +``` + + ## Confidence interval: 2.5% - 97.5% + +![](man/figures/unnamed-chunk-6-1.png) + +## Built-in Themes and Color Palettes + +besthr includes built-in themes and colorblind-safe color palettes that +can be applied directly through the `plot()` function. + +### Theme Options + +Use the `theme` parameter to change the overall visual style: + +- `"classic"` (default) - The original besthr appearance +- `"modern"` - A cleaner, contemporary style with refined typography and + grid + +``` r +# Classic theme (default - same as before) +plot(hr_est_1, theme = "classic") +``` + + ## Confidence interval: 2.5% - 97.5% + +![](man/figures/unnamed-chunk-7-1.png) + +``` r +# Modern theme +plot(hr_est_1, theme = "modern") +``` + + ## Confidence interval: 2.5% - 97.5% + +![](man/figures/unnamed-chunk-7-2.png) + +### Color Palette Options + +Use the `colors` parameter to change the color palette: + +- `"default"` - Original besthr colors +- `"okabe_ito"` - Colorblind-safe Okabe-Ito palette +- `"viridis"` - Viridis color scale + +``` r +# Colorblind-safe palette +plot(hr_est_1, colors = "okabe_ito") +``` + + ## Confidence interval: 2.5% - 97.5% + +![](man/figures/unnamed-chunk-8-1.png) + +``` r +# Viridis palette +plot(hr_est_1, colors = "viridis") +``` + + ## Confidence interval: 2.5% - 97.5% + +![](man/figures/unnamed-chunk-8-2.png) + +### Combining Theme and Colors + +You can combine both options for a fully customized look: + +``` r +# Modern theme with colorblind-safe colors +plot(hr_est_1, theme = "modern", colors = "okabe_ito") +``` + + ## Confidence interval: 2.5% - 97.5% + +![](man/figures/unnamed-chunk-9-1.png) + +### Using besthr Palettes Directly + +The color palettes can also be used directly in your own ggplot2 code: + +``` r +# Get palette colors +besthr_palette("okabe_ito", n = 4) +``` + + ## [1] "#E69F00" "#56B4E9" "#009E73" "#F0E442" + +``` r +# Available palettes +besthr_palette("default", n = 3) +``` + + ## [1] "#F8766D" "#00BA38" "#619CFF" + +``` r +besthr_palette("viridis", n = 3) +``` + + ## [1] "#482576FF" "#21908CFF" "#BBDF27FF" + +## Styling Plots + +### Recommended: Use Built-in Themes and Colors + +The easiest way to style your plots is using the `theme` and `colors` +parameters: + +``` r +# Modern look with colorblind-safe colors (this is the default) +plot(hr_est_1, theme = "modern", colors = "okabe_ito") +``` + + ## Confidence interval: 2.5% - 97.5% + +![](man/figures/unnamed-chunk-11-1.png) + +``` r +# Classic appearance +plot(hr_est_1, theme = "classic", colors = "default") +``` + + ## Confidence interval: 2.5% - 97.5% + +![](man/figures/unnamed-chunk-11-2.png) + +``` r +# Viridis color scheme +plot(hr_est_1, colors = "viridis") +``` + + ## Confidence interval: 2.5% - 97.5% + +![](man/figures/unnamed-chunk-11-3.png) + +### Adding Titles and Annotations + +The plot object is a `patchwork` composition. You can add titles using +`plot_annotation()`: + +``` r +library(patchwork) + +p <- plot(hr_est_1) +``` + + ## Confidence interval: 2.5% - 97.5% + +``` r +p + plot_annotation( + title = 'HR Score Analysis', + subtitle = "Control vs Treatment", + caption = 'Generated with besthr' +) +``` + +![](man/figures/unnamed-chunk-12-1.png) + +## Raincloud Plot + +For a combined view of raw data points with summary statistics, use +`plot_raincloud()`: + +``` r +plot_raincloud(hr_est_1) +``` + +![](man/figures/unnamed-chunk-13-1.png) + +## Significance and Effect Size Annotations + +You can add statistical annotations to your plots to highlight +significant results. + +``` r +# Create example data with 3 groups and realistic variation +set.seed(42) +d_effect <- data.frame( + score = c( + sample(1:4, 12, replace = TRUE), # Group A: low scores (control) + sample(4:8, 12, replace = TRUE), # Group B: medium-high scores + sample(6:10, 12, replace = TRUE) # Group C: high scores + ), + group = rep(c("A", "B", "C"), each = 12) +) +hr_effect <- estimate(d_effect, score, group, control = "A", nits = 1000) +``` + +### Significance Stars + +Add significance stars to groups where the bootstrap confidence interval +does not overlap the control mean: + +``` r +plot(hr_effect, show_significance = TRUE) +``` + + ## Confidence interval: 2.5% - 97.5% + +![](man/figures/unnamed-chunk-15-1.png) + +### Effect Size Annotation + +Display effect size (difference from control) with confidence intervals: + +``` r +plot(hr_effect, show_effect_size = TRUE) +``` + + ## Confidence interval: 2.5% - 97.5% + +![](man/figures/unnamed-chunk-16-1.png) + +### Computing Statistics Directly + +You can also access the significance and effect size calculations +directly: + +``` r +# Compute significance +compute_significance(hr_est_1) +``` + + ## group significant p_value stars + ## 1 A NA NA + ## 2 B TRUE 0 *** + +``` r +# Compute effect sizes +compute_effect_size(hr_est_1) +``` + + ## group effect effect_ci_low effect_ci_high + ## 1 A NA NA NA + ## 2 B -8.8 -10.93125 -6.47125 + +## Summary Tables + +Generate publication-ready summary tables with `besthr_table()`: + +``` r +# Default tibble format +besthr_table(hr_effect) +``` + + ## # A tibble: 3 × 6 + ## group n mean_rank ci_low ci_high effect_size + ## + ## 1 A 12 6.88 NA NA NA + ## 2 B 12 20.0 16.7 23.2 13.1 + ## 3 C 12 28.7 25.7 31.7 21.8 + +``` r +# With significance stars +besthr_table(hr_effect, include_significance = TRUE) +``` + + ## # A tibble: 3 × 7 + ## group n mean_rank ci_low ci_high effect_size significance + ## + ## 1 A 12 6.88 NA NA NA "" + ## 2 B 12 20.0 16.7 23.2 13.1 "***" + ## 3 C 12 28.7 25.7 31.7 21.8 "***" + +### Export Formats + +Generate tables in various formats for publication: + +``` r +# Markdown format +besthr_table(hr_est_1, format = "markdown") +``` + + ## [1] "| group | n | mean_rank | ci_low | ci_high | effect_size |\n| --- | --- | --- | --- | --- | --- |\n| A | 10 | 14.9 | NA | NA | NA |\n| B | 10 | 6.1 | 3.97 | 8.43 | -8.8 |" + +``` r +# HTML format +besthr_table(hr_est_1, format = "html") + +# LaTeX format +besthr_table(hr_est_1, format = "latex") +``` + +## Publication Export + +Save your plots directly to publication-quality files: + +``` r +# Save to PNG (default 300 DPI) +save_besthr(hr_est_1, "figure1.png") + +# Save to PDF +save_besthr(hr_est_1, "figure1.pdf", width = 10, height = 8) + +# Save raincloud plot +save_besthr(hr_est_1, "raincloud.png", type = "raincloud") + +# With custom options +save_besthr(hr_est_1, "figure1.png", + theme = "modern", + colors = "okabe_ito", + width = 10, + height = 6, + dpi = 600) +``` + +Supported formats: PNG, PDF, SVG, TIFF, JPEG diff --git a/Readme.Rmd b/Readme.Rmd deleted file mode 100644 index 489519b..0000000 --- a/Readme.Rmd +++ /dev/null @@ -1,167 +0,0 @@ ---- -title: "besthr - Generating Bootstrap Estimation Distributions of HR Data" -author: "Dan MacLean" -date: "`r format(Sys.time(), '%d %B, %Y')`" -output: - github_document: - html_preview: false ---- - - - [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.3374507.svg)](https://doi.org/10.5281/zenodo.3374507) - [![R-CMD-check](https://github.com/TeamMacLean/besthr/workflows/R-CMD-check/badge.svg)](https://github.com/TeamMacLean/besthr/actions) - - -## Synopsis - -besthr is a package that creates plots showing scored HR experiments and plots of distribution of means of ranks of HR score from bootstrapping. - - -## Installation - -You can install from CRAN in the usual way. - -```{r, eval=FALSE} -install.packages("besthr") - -# or for the dev version -#install.packages("devtools") -devtools::install_github("TeamMacLean/besthr") -``` - -## Citation - -Please cite as - -> Dan MacLean. (2019). TeamMacLean/besthr: Initial Release (0.3.0). Zenodo. https://doi.org/10.5281/zenodo.3374507 - -## Simplest Use Case - Two Groups, No Replicates - -With a data frame or similar object, use the `estimate()` function to get the bootstrap estimates of the ranked data. - -`estimate()` has a basic function call as follows: - -`estimate(data, score_column_name, group_column_name, control = control_group_name)` - -The first argument after the - -```{r, fig.width=8, fig.height=6} -library(besthr) - -hr_data_1_file <- system.file("extdata", "example-data-1.csv", package = "besthr") -hr_data_1 <- readr::read_csv(hr_data_1_file) -head(hr_data_1) - -hr_est_1 <- estimate(hr_data_1, score, group, control = "A") -hr_est_1 - -plot(hr_est_1) -``` - - -### Setting Options - -You may select the group to set as the common reference control with `control`. - -```{r, fig.width=8, fig.height=6} -estimate(hr_data_1, score, group, control = "B" ) %>% - plot() -``` - -You may select the number of iterations of the bootstrap to perform with `nits` and the quantiles for the confidence interval with `low` and `high`. - -```{r, fig.width=8, fig.height=6} -estimate(hr_data_1, score, group, control = "A", nits = 1000, low = 0.4, high = 0.6) %>% - plot() -``` - -## Extended Use Case - Technical Replicates - -You can extend the `estimate()` options to specify a third column in the data that contains technical replicate information, add the technical replicate column name after the sample column. Technical replicates are automatically merged using the `mean()` function before ranking. - -```{r, fig.width=8, fig.height=6} - -hr_data_3_file <- system.file("extdata", "example-data-3.csv", package = "besthr") -hr_data_3 <- readr::read_csv(hr_data_3_file) -head(hr_data_3) - -hr_est_3 <- estimate(hr_data_3, score, sample, rep, control = "A") - -hr_est_3 - -plot(hr_est_3) - -``` - -### Alternate Plot Options - -In the case where you have use technical replicates and want to see those plotted you can use an extra plot option `which`. Set `which` to `just_data` if you wish the left panel of the plot to show all data without ranking. This will only work if you have technical replicates. - -```{r, fig.width=8, fig.height=6} - -hr_est_3 %>% - plot(which = "just_data") -``` - -## Styling Plots - -You can style plots to your own taste. The object returned from `plot()` is a `patchwork` [https://patchwork.data-imaginist.com/](https://patchwork.data-imaginist.com/) object that composes two separate plots, the dot plot and the bootstrap percentile plot, which are themselves `ggplot` objects. So you can use a mixture of `patchwork` annotations functions for whole plot labels and `ggplot` themes for individual elements. - -### Adding annotations. - -You can use the `patchwork` `plot_annotation()` function to add titles - -```{r, fig.width=8, fig.height=6} -library(patchwork) - -p <- plot(hr_est_1) - -p + plot_annotation(title = 'A stylish besthr plot', - subtitle = "better than ever", - caption = 'Though this example is not meaningful') -p - - -``` - - -### Targetting a subplot to make theme changes - -You can change the style of the individual plot elements using subsetting syntax `[[]]` . The dot plot can be addressed within the `patchwork` object using index 1 within the `patchwork` object `p[[1]]`, and the percentile plot using `p[[2]]`. You must add to the existing subplot then assign the result back to see the difference in the plot. Here's an example that uses `theme()` to restyle the y-axis text of the dot plot - -```{r, fig.width=8, fig.height=6} -library(ggplot2) -p[[1]] <- p[[1]] + theme(axis.title.y = element_text(family = "Times", colour="blue", size=24)) -p -``` - - -### Changing the scale colours of a subplot - -You can change the colours used by the scales in the same way using the `scale` functions, though as the type of scale is different for the dot plot and bootstrap plot you will need to apply a different scale for each. - -For the dot plot, use a discrete scale e.g `scale_colour_manual()`, `scale_colour_viridis_d()` or `scale_colour_brewer(type = "qual")` - -```{r, fig.width=8, fig.height=6} -p[[1]] <- p[[1]] + scale_colour_manual(values = c("blue", "#440000")) -p - -p[[1]] <- p[[1]] + scale_colour_viridis_d() -p - -p[[1]] <- p[[1]] + scale_colour_brewer(type="qual", palette="Accent") -p -``` - -For the percentile plot, use only `scale_colour_manual()` with specified colours. Annoyingly, this rewrites the other values associated with the scale each time, so you'll need to replace those. - -```{r, fig.width=8, fig.height=6} -p[[2]] <- p[[2]] + scale_fill_manual( - values = c("blue", "pink", "yellow"), - name = "bootstrap percentile", labels=c("lower", "non-significant", "higher"), - guide = guide_legend(reverse=TRUE) - ) -p -``` - - diff --git a/Readme.md b/Readme.md deleted file mode 100644 index 82fe716..0000000 --- a/Readme.md +++ /dev/null @@ -1,335 +0,0 @@ -besthr - Generating Bootstrap Estimation Distributions of HR Data -================ -Dan MacLean -06 October, 2022 - - -[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.3374507.svg)](https://doi.org/10.5281/zenodo.3374507) -[![R-CMD-check](https://github.com/TeamMacLean/besthr/workflows/R-CMD-check/badge.svg)](https://github.com/TeamMacLean/besthr/actions) - - -## Synopsis - -besthr is a package that creates plots showing scored HR experiments and -plots of distribution of means of ranks of HR score from bootstrapping. - -## Installation - -You can install from CRAN in the usual way. - -``` r -install.packages("besthr") - -# or for the dev version -#install.packages("devtools") -devtools::install_github("TeamMacLean/besthr") -``` - -## Citation - -Please cite as - -> Dan MacLean. (2019). TeamMacLean/besthr: Initial Release (0.3.0). -> Zenodo. - -## Simplest Use Case - Two Groups, No Replicates - -With a data frame or similar object, use the `estimate()` function to -get the bootstrap estimates of the ranked data. - -`estimate()` has a basic function call as follows: - -`estimate(data, score_column_name, group_column_name, control = control_group_name)` - -The first argument after the - -``` r -library(besthr) - -hr_data_1_file <- system.file("extdata", "example-data-1.csv", package = "besthr") -hr_data_1 <- readr::read_csv(hr_data_1_file) -``` - - ## Rows: 20 Columns: 2 - ## ── Column specification ──────────────────────────────────────────────────────── - ## Delimiter: "," - ## chr (1): group - ## dbl (1): score - ## - ## ℹ Use `spec()` to retrieve the full column specification for this data. - ## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message. - -``` r -head(hr_data_1) -``` - - ## # A tibble: 6 × 2 - ## score group - ## - ## 1 10 A - ## 2 9 A - ## 3 10 A - ## 4 10 A - ## 5 8 A - ## 6 8 A - -``` r -hr_est_1 <- estimate(hr_data_1, score, group, control = "A") -hr_est_1 -``` - - ## besthr (HR Rank Score Analysis with Bootstrap Estimation) - ## ========================================================= - ## - ## Control: A - ## - ## Unpaired mean rank difference of A (14.9, n=10) minus B (6.1, n=10) - ## 8.8 - ## Confidence Intervals (0.025, 0.975) - ## 4.16625, 7.82625 - ## - ## 100 bootstrap resamples. - -``` r -plot(hr_est_1) -``` - - ## Picking joint bandwidth of 0.376 - -![](Readme_files/figure-gfm/unnamed-chunk-2-1.png) - -### Setting Options - -You may select the group to set as the common reference control with -`control`. - -``` r -estimate(hr_data_1, score, group, control = "B" ) %>% - plot() -``` - - ## Picking joint bandwidth of 0.383 - -![](Readme_files/figure-gfm/unnamed-chunk-3-1.png) - -You may select the number of iterations of the bootstrap to perform with -`nits` and the quantiles for the confidence interval with `low` and -`high`. - -``` r -estimate(hr_data_1, score, group, control = "A", nits = 1000, low = 0.4, high = 0.6) %>% - plot() -``` - - ## Picking joint bandwidth of 0.261 - -![](Readme_files/figure-gfm/unnamed-chunk-4-1.png) - -## Extended Use Case - Technical Replicates - -You can extend the `estimate()` options to specify a third column in the -data that contains technical replicate information, add the technical -replicate column name after the sample column. Technical replicates are -automatically merged using the `mean()` function before ranking. - -``` r -hr_data_3_file <- system.file("extdata", "example-data-3.csv", package = "besthr") -hr_data_3 <- readr::read_csv(hr_data_3_file) -``` - - ## Rows: 36 Columns: 3 - ## ── Column specification ──────────────────────────────────────────────────────── - ## Delimiter: "," - ## chr (1): sample - ## dbl (2): score, rep - ## - ## ℹ Use `spec()` to retrieve the full column specification for this data. - ## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message. - -``` r -head(hr_data_3) -``` - - ## # A tibble: 6 × 3 - ## score sample rep - ## - ## 1 8 A 1 - ## 2 9 A 1 - ## 3 8 A 1 - ## 4 10 A 1 - ## 5 8 A 2 - ## 6 8 A 2 - -``` r -hr_est_3 <- estimate(hr_data_3, score, sample, rep, control = "A") - -hr_est_3 -``` - - ## besthr (HR Rank Score Analysis with Bootstrap Estimation) - ## ========================================================= - ## - ## Control: A - ## - ## Unpaired mean rank difference of A (5, n=3) minus B (2, n=3) - ## 3 - ## Confidence Intervals (0.025, 0.975) - ## 1.33333333333333, 2.66666666666667 - ## - ## Unpaired mean rank difference of A (5, n=3) minus C (8, n=3) - ## -3 - ## Confidence Intervals (0.025, 0.975) - ## 7, 9 - ## - ## 100 bootstrap resamples. - -``` r -plot(hr_est_3) -``` - - ## Picking joint bandwidth of 0.163 - -![](Readme_files/figure-gfm/unnamed-chunk-5-1.png) - -### Alternate Plot Options - -In the case where you have use technical replicates and want to see -those plotted you can use an extra plot option `which`. Set `which` to -`just_data` if you wish the left panel of the plot to show all data -without ranking. This will only work if you have technical replicates. - -``` r -hr_est_3 %>% - plot(which = "just_data") -``` - - ## Picking joint bandwidth of 0.163 - -![](Readme_files/figure-gfm/unnamed-chunk-6-1.png) - -## Styling Plots - -You can style plots to your own taste. The object returned from `plot()` -is a `patchwork` object that -composes two separate plots, the dot plot and the bootstrap percentile -plot, which are themselves `ggplot` objects. So you can use a mixture of -`patchwork` annotations functions for whole plot labels and `ggplot` -themes for individual elements. - -### Adding annotations. - -You can use the `patchwork` `plot_annotation()` function to add titles - -``` r -library(patchwork) - -p <- plot(hr_est_1) - -p + plot_annotation(title = 'A stylish besthr plot', - subtitle = "better than ever", - caption = 'Though this example is not meaningful') -``` - - ## Picking joint bandwidth of 0.376 - -![](Readme_files/figure-gfm/unnamed-chunk-7-1.png) - -``` r -p -``` - - ## Picking joint bandwidth of 0.376 - -![](Readme_files/figure-gfm/unnamed-chunk-7-2.png) - -### Targetting a subplot to make theme changes - -You can change the style of the individual plot elements using -subsetting syntax `[[]]` . The dot plot can be addressed within the -`patchwork` object using index 1 within the `patchwork` object `p[[1]]`, -and the percentile plot using `p[[2]]`. You must add to the existing -subplot then assign the result back to see the difference in the plot. -Here’s an example that uses `theme()` to restyle the y-axis text of the -dot plot - -``` r -library(ggplot2) -p[[1]] <- p[[1]] + theme(axis.title.y = element_text(family = "Times", colour="blue", size=24)) -p -``` - - ## Picking joint bandwidth of 0.376 - -![](Readme_files/figure-gfm/unnamed-chunk-8-1.png) - -### Changing the scale colours of a subplot - -You can change the colours used by the scales in the same way using the -`scale` functions, though as the type of scale is different for the dot -plot and bootstrap plot you will need to apply a different scale for -each. - -For the dot plot, use a discrete scale e.g `scale_colour_manual()`, -`scale_colour_viridis_d()` or `scale_colour_brewer(type = "qual")` - -``` r -p[[1]] <- p[[1]] + scale_colour_manual(values = c("blue", "#440000")) -p -``` - - ## Picking joint bandwidth of 0.376 - -![](Readme_files/figure-gfm/unnamed-chunk-9-1.png) - -``` r -p[[1]] <- p[[1]] + scale_colour_viridis_d() -``` - - ## Scale for 'colour' is already present. Adding another scale for 'colour', - ## which will replace the existing scale. - -``` r -p -``` - - ## Picking joint bandwidth of 0.376 - -![](Readme_files/figure-gfm/unnamed-chunk-9-2.png) - -``` r -p[[1]] <- p[[1]] + scale_colour_brewer(type="qual", palette="Accent") -``` - - ## Scale for 'colour' is already present. Adding another scale for 'colour', - ## which will replace the existing scale. - -``` r -p -``` - - ## Picking joint bandwidth of 0.376 - -![](Readme_files/figure-gfm/unnamed-chunk-9-3.png) - -For the percentile plot, use only `scale_colour_manual()` with specified -colours. Annoyingly, this rewrites the other values associated with the -scale each time, so you’ll need to replace those. - -``` r -p[[2]] <- p[[2]] + scale_fill_manual( - values = c("blue", "pink", "yellow"), - name = "bootstrap percentile", labels=c("lower", "non-significant", "higher"), - guide = guide_legend(reverse=TRUE) - ) -``` - - ## Scale for 'fill' is already present. Adding another scale for 'fill', which - ## will replace the existing scale. - -``` r -p -``` - - ## Picking joint bandwidth of 0.376 - -![](Readme_files/figure-gfm/unnamed-chunk-10-1.png) diff --git a/Readme_files/figure-gfm/unnamed-chunk-1-1.png b/Readme_files/figure-gfm/unnamed-chunk-1-1.png deleted file mode 100644 index db9daed..0000000 Binary files a/Readme_files/figure-gfm/unnamed-chunk-1-1.png and /dev/null differ diff --git a/Readme_files/figure-gfm/unnamed-chunk-10-1.png b/Readme_files/figure-gfm/unnamed-chunk-10-1.png deleted file mode 100644 index 2436e86..0000000 Binary files a/Readme_files/figure-gfm/unnamed-chunk-10-1.png and /dev/null differ diff --git a/Readme_files/figure-gfm/unnamed-chunk-10-2.png b/Readme_files/figure-gfm/unnamed-chunk-10-2.png deleted file mode 100644 index 9c77798..0000000 Binary files a/Readme_files/figure-gfm/unnamed-chunk-10-2.png and /dev/null differ diff --git a/Readme_files/figure-gfm/unnamed-chunk-10-3.png b/Readme_files/figure-gfm/unnamed-chunk-10-3.png deleted file mode 100644 index f52e64f..0000000 Binary files a/Readme_files/figure-gfm/unnamed-chunk-10-3.png and /dev/null differ diff --git a/Readme_files/figure-gfm/unnamed-chunk-11-1.png b/Readme_files/figure-gfm/unnamed-chunk-11-1.png deleted file mode 100644 index 5094dfa..0000000 Binary files a/Readme_files/figure-gfm/unnamed-chunk-11-1.png and /dev/null differ diff --git a/Readme_files/figure-gfm/unnamed-chunk-2-1.png b/Readme_files/figure-gfm/unnamed-chunk-2-1.png deleted file mode 100644 index 8a2bd66..0000000 Binary files a/Readme_files/figure-gfm/unnamed-chunk-2-1.png and /dev/null differ diff --git a/Readme_files/figure-gfm/unnamed-chunk-3-1.png b/Readme_files/figure-gfm/unnamed-chunk-3-1.png deleted file mode 100644 index 5de63c3..0000000 Binary files a/Readme_files/figure-gfm/unnamed-chunk-3-1.png and /dev/null differ diff --git a/Readme_files/figure-gfm/unnamed-chunk-4-1.png b/Readme_files/figure-gfm/unnamed-chunk-4-1.png deleted file mode 100644 index 15c7f9b..0000000 Binary files a/Readme_files/figure-gfm/unnamed-chunk-4-1.png and /dev/null differ diff --git a/Readme_files/figure-gfm/unnamed-chunk-5-1.png b/Readme_files/figure-gfm/unnamed-chunk-5-1.png deleted file mode 100644 index 9456f84..0000000 Binary files a/Readme_files/figure-gfm/unnamed-chunk-5-1.png and /dev/null differ diff --git a/Readme_files/figure-gfm/unnamed-chunk-6-1.png b/Readme_files/figure-gfm/unnamed-chunk-6-1.png deleted file mode 100644 index 37a1b7b..0000000 Binary files a/Readme_files/figure-gfm/unnamed-chunk-6-1.png and /dev/null differ diff --git a/Readme_files/figure-gfm/unnamed-chunk-6-2.png b/Readme_files/figure-gfm/unnamed-chunk-6-2.png deleted file mode 100644 index db9daed..0000000 Binary files a/Readme_files/figure-gfm/unnamed-chunk-6-2.png and /dev/null differ diff --git a/Readme_files/figure-gfm/unnamed-chunk-7-1.png b/Readme_files/figure-gfm/unnamed-chunk-7-1.png deleted file mode 100644 index 0916d44..0000000 Binary files a/Readme_files/figure-gfm/unnamed-chunk-7-1.png and /dev/null differ diff --git a/Readme_files/figure-gfm/unnamed-chunk-7-2.png b/Readme_files/figure-gfm/unnamed-chunk-7-2.png deleted file mode 100644 index 8a2bd66..0000000 Binary files a/Readme_files/figure-gfm/unnamed-chunk-7-2.png and /dev/null differ diff --git a/Readme_files/figure-gfm/unnamed-chunk-8-1.png b/Readme_files/figure-gfm/unnamed-chunk-8-1.png deleted file mode 100644 index ff86c89..0000000 Binary files a/Readme_files/figure-gfm/unnamed-chunk-8-1.png and /dev/null differ diff --git a/Readme_files/figure-gfm/unnamed-chunk-8-2.png b/Readme_files/figure-gfm/unnamed-chunk-8-2.png deleted file mode 100644 index 0eec916..0000000 Binary files a/Readme_files/figure-gfm/unnamed-chunk-8-2.png and /dev/null differ diff --git a/Readme_files/figure-gfm/unnamed-chunk-8-3.png b/Readme_files/figure-gfm/unnamed-chunk-8-3.png deleted file mode 100644 index 525cd65..0000000 Binary files a/Readme_files/figure-gfm/unnamed-chunk-8-3.png and /dev/null differ diff --git a/Readme_files/figure-gfm/unnamed-chunk-9-1.png b/Readme_files/figure-gfm/unnamed-chunk-9-1.png deleted file mode 100644 index dd429f6..0000000 Binary files a/Readme_files/figure-gfm/unnamed-chunk-9-1.png and /dev/null differ diff --git a/Readme_files/figure-gfm/unnamed-chunk-9-2.png b/Readme_files/figure-gfm/unnamed-chunk-9-2.png deleted file mode 100644 index f32471a..0000000 Binary files a/Readme_files/figure-gfm/unnamed-chunk-9-2.png and /dev/null differ diff --git a/Readme_files/figure-gfm/unnamed-chunk-9-3.png b/Readme_files/figure-gfm/unnamed-chunk-9-3.png deleted file mode 100644 index 7bddd22..0000000 Binary files a/Readme_files/figure-gfm/unnamed-chunk-9-3.png and /dev/null differ diff --git a/_pkgdown.yml b/_pkgdown.yml new file mode 100644 index 0000000..9af58dc --- /dev/null +++ b/_pkgdown.yml @@ -0,0 +1,21 @@ +url: https://teammacLean.github.io/besthr/ + +template: + bootstrap: 5 + + +home: + title: besthr - Bootstrap Estimation for HR Data + description: > + Generate bootstrap estimation distributions of HR (Hypersensitive Response) + data from plant pathology experiments with publication-ready visualizations. + +navbar: + structure: + left: [intro, reference, articles, news] + right: [search, github] + components: + github: + icon: fab fa-github fa-lg + href: https://github.com/TeamMacLean/besthr + aria-label: GitHub \ No newline at end of file diff --git a/man/apply_besthr_theme.Rd b/man/apply_besthr_theme.Rd new file mode 100644 index 0000000..bcbd2c8 --- /dev/null +++ b/man/apply_besthr_theme.Rd @@ -0,0 +1,33 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/themes.R +\name{apply_besthr_theme} +\alias{apply_besthr_theme} +\title{Apply besthr theme consistently} +\usage{ +apply_besthr_theme(p, config, include_fill = TRUE, include_color = TRUE) +} +\arguments{ +\item{p}{A ggplot object} + +\item{config}{A besthr_plot_config object} + +\item{include_fill}{Logical, whether to apply fill scale (default TRUE)} + +\item{include_color}{Logical, whether to apply color scale (default TRUE)} +} +\value{ +The ggplot object with theme and scales applied +} +\description{ +Applies the besthr theme and color scales to a ggplot object based on +configuration settings. This ensures consistent theming across all plot +components. +} +\examples{ +library(ggplot2) +p <- ggplot(mtcars, aes(mpg, wt, color = factor(cyl))) + + geom_point() +cfg <- besthr_plot_config(theme_style = "modern", color_palette = "okabe_ito") +apply_besthr_theme(p, cfg) + +} diff --git a/man/besthr_data_view.Rd b/man/besthr_data_view.Rd new file mode 100644 index 0000000..596f199 --- /dev/null +++ b/man/besthr_data_view.Rd @@ -0,0 +1,30 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/plot-layers.R +\name{besthr_data_view} +\alias{besthr_data_view} +\title{Create a unified data view for besthr plotting} +\usage{ +besthr_data_view(hrest, config = NULL) +} +\arguments{ +\item{hrest}{An hrest object from \code{\link{estimate}}} + +\item{config}{A besthr_plot_config object (optional). If NULL, uses defaults.} +} +\value{ +An object of class "besthr_data_view" containing ranked data, + original data, bootstrap samples, group means, confidence intervals, + group sample sizes, unified rank limits, group column symbol, column info, + control group name, and quantile values. +} +\description{ +Extracts and organizes data from an hrest object for plotting. Computes +unified axis limits that ensure alignment between observation and bootstrap +panels. +} +\examples{ +d <- make_data() +hr <- estimate(d, score, group) +dv <- besthr_data_view(hr) + +} diff --git a/man/besthr_palette.Rd b/man/besthr_palette.Rd new file mode 100644 index 0000000..dfb4035 --- /dev/null +++ b/man/besthr_palette.Rd @@ -0,0 +1,30 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/themes.R +\name{besthr_palette} +\alias{besthr_palette} +\title{besthr color palettes} +\usage{ +besthr_palette(palette = "default", n = NULL) +} +\arguments{ +\item{palette}{Character string specifying the palette. Options are: +\itemize{ + \item "default" - Original besthr colors + \item "okabe_ito" - Colorblind-safe Okabe-Ito palette + \item "viridis" - Viridis color scale +}} + +\item{n}{Number of colors to return. If NULL, returns all colors in palette.} +} +\value{ +A character vector of hex color codes +} +\description{ +Returns a color palette suitable for besthr visualizations. The default +palette uses Okabe-Ito colorblind-safe colors. +} +\examples{ +besthr_palette() +besthr_palette("okabe_ito", 3) + +} diff --git a/man/besthr_plot_config.Rd b/man/besthr_plot_config.Rd new file mode 100644 index 0000000..7fe3717 --- /dev/null +++ b/man/besthr_plot_config.Rd @@ -0,0 +1,60 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/plot-config.R +\name{besthr_plot_config} +\alias{besthr_plot_config} +\title{Create a besthr plot configuration} +\usage{ +besthr_plot_config( + panel_widths = c(1, 1), + panel_spacing = grid::unit(0.5, "cm"), + y_limits = NULL, + y_expand = 0.05, + point_size_range = c(2, 8), + point_alpha = 0.8, + mean_line_type = 3, + mean_line_width = 1, + density_alpha = 0.7, + density_style = "points", + theme_style = "modern", + color_palette = "okabe_ito" +) +} +\arguments{ +\item{panel_widths}{Numeric vector of relative panel widths for patchwork layout.} + +\item{panel_spacing}{A grid unit specifying spacing between panels.} + +\item{y_limits}{Numeric vector of length 2 for y-axis limits, or NULL for auto.} + +\item{y_expand}{Numeric giving proportional expansion of y-axis limits.} + +\item{point_size_range}{Numeric vector of length 2 for min/max point sizes.} + +\item{point_alpha}{Numeric between 0 and 1 for point transparency.} + +\item{mean_line_type}{Line type for mean indicator lines.} + +\item{mean_line_width}{Line width for mean indicator lines.} + +\item{density_alpha}{Numeric between 0 and 1 for density plot transparency.} + +\item{density_style}{Character: "points" (default, jittered bootstrap points), +"gradient" (density with CI shading), or "solid" (single color density).} + +\item{theme_style}{Character: "classic" or "modern".} + +\item{color_palette}{Character: "default", "okabe_ito", or "viridis".} +} +\value{ +An object of class "besthr_plot_config" containing all plot settings. +} +\description{ +Creates a configuration object that controls the appearance and behavior +of besthr plots. All parameters have defaults that reproduce the original +besthr appearance for backward compatibility. +} +\examples{ +cfg <- besthr_plot_config() +cfg <- besthr_plot_config(panel_widths = c(2, 1), theme_style = "modern") + +} diff --git a/man/besthr_style.Rd b/man/besthr_style.Rd new file mode 100644 index 0000000..b56c3da --- /dev/null +++ b/man/besthr_style.Rd @@ -0,0 +1,39 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/themes.R +\name{besthr_style} +\alias{besthr_style} +\title{Get a preset plot style} +\usage{ +besthr_style(style = "default") +} +\arguments{ +\item{style}{Character string specifying the style preset: +\itemize{ + \item "default" - Modern theme with colorblind-safe colors (recommended) + \item "classic" - Original besthr appearance for backward compatibility + \item "publication" - Clean style suitable for journal figures + \item "presentation" - Larger elements for slides + \item "density" - Uses gradient density instead of points for bootstrap +}} +} +\value{ +A \code{besthr_plot_config} object +} +\description{ +Returns a pre-configured \code{besthr_plot_config} object with sensible +defaults for common use cases. This is the easiest way to customize +besthr plot appearance without understanding all the configuration options. +} +\examples{ +d <- make_data() +hr <- estimate(d, score, group) + +# Quick styling with presets +plot(hr, config = besthr_style("publication")) +plot(hr, config = besthr_style("presentation")) +plot(hr, config = besthr_style("density")) + +# Same as default +plot(hr, config = besthr_style("default")) + +} diff --git a/man/besthr_table.Rd b/man/besthr_table.Rd new file mode 100644 index 0000000..41a7f5e --- /dev/null +++ b/man/besthr_table.Rd @@ -0,0 +1,36 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/table.R +\name{besthr_table} +\alias{besthr_table} +\title{Generate a summary table from besthr results} +\usage{ +besthr_table( + hrest, + format = "tibble", + digits = 2, + include_significance = FALSE +) +} +\arguments{ +\item{hrest}{An hrest object from \code{\link{estimate}}} + +\item{format}{Output format: "tibble" (default), "markdown", "html", or "latex"} + +\item{digits}{Number of decimal places for rounding (default 2)} + +\item{include_significance}{Logical, whether to include significance stars (default FALSE)} +} +\value{ +A tibble (if format = "tibble") or character string (other formats) +} +\description{ +Creates a publication-ready summary table containing group statistics, +confidence intervals, and optionally effect sizes and significance. +} +\examples{ +d <- make_data() +hr <- estimate(d, score, group) +besthr_table(hr) +besthr_table(hr, format = "markdown") + +} diff --git a/man/build_bootstrap_panel.Rd b/man/build_bootstrap_panel.Rd new file mode 100644 index 0000000..ee1c974 --- /dev/null +++ b/man/build_bootstrap_panel.Rd @@ -0,0 +1,29 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/plot-panels.R +\name{build_bootstrap_panel} +\alias{build_bootstrap_panel} +\title{Build the bootstrap distribution panel} +\usage{ +build_bootstrap_panel(data_view, config) +} +\arguments{ +\item{data_view}{A besthr_data_view object} + +\item{config}{A besthr_plot_config object} +} +\value{ +A ggplot object +} +\description{ +Creates a ggplot showing ridge density plots of bootstrap distributions with +confidence interval shading. +} +\examples{ +d <- make_data() +hr <- estimate(d, score, group) +dv <- besthr_data_view(hr) +cfg <- besthr_plot_config() + +build_bootstrap_panel(dv, cfg) + +} diff --git a/man/build_observation_panel.Rd b/man/build_observation_panel.Rd new file mode 100644 index 0000000..df96c8f --- /dev/null +++ b/man/build_observation_panel.Rd @@ -0,0 +1,32 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/plot-panels.R +\name{build_observation_panel} +\alias{build_observation_panel} +\title{Build the observation panel} +\usage{ +build_observation_panel(data_view, config, which = "rank_simulation") +} +\arguments{ +\item{data_view}{A besthr_data_view object} + +\item{config}{A besthr_plot_config object} + +\item{which}{Character specifying panel type: "rank_simulation" for averaged +ranked data, or "just_data" for raw scores with tech reps.} +} +\value{ +A ggplot object +} +\description{ +Creates a ggplot showing either ranked observations (averaged tech reps) or +raw scores with technical replicates displayed. +} +\examples{ +d <- make_data() +hr <- estimate(d, score, group) +dv <- besthr_data_view(hr) +cfg <- besthr_plot_config() + +build_observation_panel(dv, cfg, "rank_simulation") + +} diff --git a/man/build_ranked_panel.Rd b/man/build_ranked_panel.Rd new file mode 100644 index 0000000..4a970fc --- /dev/null +++ b/man/build_ranked_panel.Rd @@ -0,0 +1,20 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/plot-panels.R +\name{build_ranked_panel} +\alias{build_ranked_panel} +\title{Build the ranked observation panel} +\usage{ +build_ranked_panel(data_view, config) +} +\arguments{ +\item{data_view}{A besthr_data_view object} + +\item{config}{A besthr_plot_config object} +} +\value{ +A ggplot object +} +\description{ +Internal function to build the panel showing ranked observations. +} +\keyword{internal} diff --git a/man/build_tech_rep_panel.Rd b/man/build_tech_rep_panel.Rd new file mode 100644 index 0000000..7be653d --- /dev/null +++ b/man/build_tech_rep_panel.Rd @@ -0,0 +1,20 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/plot-panels.R +\name{build_tech_rep_panel} +\alias{build_tech_rep_panel} +\title{Build the technical replicate panel} +\usage{ +build_tech_rep_panel(data_view, config) +} +\arguments{ +\item{data_view}{A besthr_data_view object} + +\item{config}{A besthr_plot_config object} +} +\value{ +A ggplot object +} +\description{ +Internal function to build the panel showing raw scores with tech reps. +} +\keyword{internal} diff --git a/man/ci_fill_colors.Rd b/man/ci_fill_colors.Rd new file mode 100644 index 0000000..cbf8ae7 --- /dev/null +++ b/man/ci_fill_colors.Rd @@ -0,0 +1,20 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/themes.R +\name{ci_fill_colors} +\alias{ci_fill_colors} +\title{Confidence interval fill colors} +\usage{ +ci_fill_colors(style = "default") +} +\arguments{ +\item{style}{Character string: "default" for original colors, "modern" for +updated colors} +} +\value{ +A named character vector of hex colors for low, middle, high regions +} +\description{ +Returns the fill colors used for confidence interval regions in bootstrap +distribution plots. +} +\keyword{internal} diff --git a/man/compose_besthr_panels.Rd b/man/compose_besthr_panels.Rd new file mode 100644 index 0000000..df9bfb6 --- /dev/null +++ b/man/compose_besthr_panels.Rd @@ -0,0 +1,31 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/plot-panels.R +\name{compose_besthr_panels} +\alias{compose_besthr_panels} +\title{Compose besthr panels} +\usage{ +compose_besthr_panels(panels, config) +} +\arguments{ +\item{panels}{A list of ggplot objects to compose} + +\item{config}{A besthr_plot_config object} +} +\value{ +A patchwork object +} +\description{ +Combines observation and bootstrap panels using patchwork with proper +alignment and shared legends. +} +\examples{ +d <- make_data() +hr <- estimate(d, score, group) +dv <- besthr_data_view(hr) +cfg <- besthr_plot_config() + +p1 <- build_observation_panel(dv, cfg) +p2 <- build_bootstrap_panel(dv, cfg) +compose_besthr_panels(list(p1, p2), cfg) + +} diff --git a/man/compute_effect_size.Rd b/man/compute_effect_size.Rd new file mode 100644 index 0000000..72bc02e --- /dev/null +++ b/man/compute_effect_size.Rd @@ -0,0 +1,24 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/significance.R +\name{compute_effect_size} +\alias{compute_effect_size} +\title{Compute effect sizes from bootstrap distributions} +\usage{ +compute_effect_size(hrest) +} +\arguments{ +\item{hrest}{An hrest object from \code{\link{estimate}}} +} +\value{ +A data frame with columns: group, effect, effect_ci_low, effect_ci_high +} +\description{ +Calculates the effect size (difference from control) for each treatment group +with bootstrap confidence intervals. +} +\examples{ +d <- make_data() +hr <- estimate(d, score, group, nits = 500) +compute_effect_size(hr) + +} diff --git a/man/compute_significance.Rd b/man/compute_significance.Rd new file mode 100644 index 0000000..262c207 --- /dev/null +++ b/man/compute_significance.Rd @@ -0,0 +1,24 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/significance.R +\name{compute_significance} +\alias{compute_significance} +\title{Compute significance from bootstrap distributions} +\usage{ +compute_significance(hrest) +} +\arguments{ +\item{hrest}{An hrest object from \code{\link{estimate}}} +} +\value{ +A data frame with columns: group, significant (logical), p_value, stars +} +\description{ +Determines statistical significance by checking if the bootstrap confidence +interval for each treatment group overlaps with the control group's mean rank. +} +\examples{ +d <- make_data() +hr <- estimate(d, score, group, nits = 500) +compute_significance(hr) + +} diff --git a/man/derive_ci_colors.Rd b/man/derive_ci_colors.Rd new file mode 100644 index 0000000..d58b2fa --- /dev/null +++ b/man/derive_ci_colors.Rd @@ -0,0 +1,29 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/themes.R +\name{derive_ci_colors} +\alias{derive_ci_colors} +\title{Derive CI colors based on palette and theme} +\usage{ +derive_ci_colors(palette = "default", theme_style = "classic") +} +\arguments{ +\item{palette}{Character string specifying the color palette: "default", +"okabe_ito", or "viridis"} + +\item{theme_style}{Character string specifying the theme: "classic" or "modern"} +} +\value{ +A character vector of three hex colors with alpha for low, middle, + and high CI regions +} +\description{ +Computes confidence interval fill colors that harmonize with the selected +color palette and theme style. This ensures visual consistency between the +observation panel colors and the bootstrap density shading. +} +\examples{ +derive_ci_colors("default", "classic") +derive_ci_colors("okabe_ito", "modern") +derive_ci_colors("viridis", "classic") + +} diff --git a/man/dot_plot.Rd b/man/dot_plot.Rd index ccc2e0b..70345a0 100644 --- a/man/dot_plot.Rd +++ b/man/dot_plot.Rd @@ -1,15 +1,19 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/functions.R +% Please edit documentation in R/plot-hrest.R \name{dot_plot} \alias{dot_plot} \title{dot plot of ranked data without technical replicates} \usage{ -dot_plot(hrest, group_col) +dot_plot(hrest, group_col, theme_style = "modern", color_palette = "okabe_ito") } \arguments{ \item{hrest}{the hrest object from \code{estimate}} \item{group_col}{quoted group column name} + +\item{theme_style}{character specifying the theme style} + +\item{color_palette}{character specifying the color palette} } \description{ \code{dot_plot} returns a ggplot object of ranked data with group on the diff --git a/man/figures/unnamed-chunk-11-1.png b/man/figures/unnamed-chunk-11-1.png new file mode 100644 index 0000000..3396116 Binary files /dev/null and b/man/figures/unnamed-chunk-11-1.png differ diff --git a/man/figures/unnamed-chunk-11-2.png b/man/figures/unnamed-chunk-11-2.png new file mode 100644 index 0000000..9ab0dcf Binary files /dev/null and b/man/figures/unnamed-chunk-11-2.png differ diff --git a/man/figures/unnamed-chunk-11-3.png b/man/figures/unnamed-chunk-11-3.png new file mode 100644 index 0000000..bad09c2 Binary files /dev/null and b/man/figures/unnamed-chunk-11-3.png differ diff --git a/man/figures/unnamed-chunk-12-1.png b/man/figures/unnamed-chunk-12-1.png new file mode 100644 index 0000000..6c2f0d6 Binary files /dev/null and b/man/figures/unnamed-chunk-12-1.png differ diff --git a/man/figures/unnamed-chunk-13-1.png b/man/figures/unnamed-chunk-13-1.png new file mode 100644 index 0000000..bb286f1 Binary files /dev/null and b/man/figures/unnamed-chunk-13-1.png differ diff --git a/man/figures/unnamed-chunk-15-1.png b/man/figures/unnamed-chunk-15-1.png new file mode 100644 index 0000000..8f78169 Binary files /dev/null and b/man/figures/unnamed-chunk-15-1.png differ diff --git a/man/figures/unnamed-chunk-16-1.png b/man/figures/unnamed-chunk-16-1.png new file mode 100644 index 0000000..c1ba720 Binary files /dev/null and b/man/figures/unnamed-chunk-16-1.png differ diff --git a/man/figures/unnamed-chunk-2-1.png b/man/figures/unnamed-chunk-2-1.png new file mode 100644 index 0000000..09bb810 Binary files /dev/null and b/man/figures/unnamed-chunk-2-1.png differ diff --git a/man/figures/unnamed-chunk-3-1.png b/man/figures/unnamed-chunk-3-1.png new file mode 100644 index 0000000..e687bd2 Binary files /dev/null and b/man/figures/unnamed-chunk-3-1.png differ diff --git a/man/figures/unnamed-chunk-4-1.png b/man/figures/unnamed-chunk-4-1.png new file mode 100644 index 0000000..aae75de Binary files /dev/null and b/man/figures/unnamed-chunk-4-1.png differ diff --git a/man/figures/unnamed-chunk-5-1.png b/man/figures/unnamed-chunk-5-1.png new file mode 100644 index 0000000..47019da Binary files /dev/null and b/man/figures/unnamed-chunk-5-1.png differ diff --git a/man/figures/unnamed-chunk-6-1.png b/man/figures/unnamed-chunk-6-1.png new file mode 100644 index 0000000..dff0b34 Binary files /dev/null and b/man/figures/unnamed-chunk-6-1.png differ diff --git a/man/figures/unnamed-chunk-7-1.png b/man/figures/unnamed-chunk-7-1.png new file mode 100644 index 0000000..f4730f6 Binary files /dev/null and b/man/figures/unnamed-chunk-7-1.png differ diff --git a/man/figures/unnamed-chunk-7-2.png b/man/figures/unnamed-chunk-7-2.png new file mode 100644 index 0000000..730f300 Binary files /dev/null and b/man/figures/unnamed-chunk-7-2.png differ diff --git a/man/figures/unnamed-chunk-8-1.png b/man/figures/unnamed-chunk-8-1.png new file mode 100644 index 0000000..45c3946 Binary files /dev/null and b/man/figures/unnamed-chunk-8-1.png differ diff --git a/man/figures/unnamed-chunk-8-2.png b/man/figures/unnamed-chunk-8-2.png new file mode 100644 index 0000000..d7bb893 Binary files /dev/null and b/man/figures/unnamed-chunk-8-2.png differ diff --git a/man/figures/unnamed-chunk-9-1.png b/man/figures/unnamed-chunk-9-1.png new file mode 100644 index 0000000..df1b45b Binary files /dev/null and b/man/figures/unnamed-chunk-9-1.png differ diff --git a/man/format_table.Rd b/man/format_table.Rd new file mode 100644 index 0000000..d46e1cf --- /dev/null +++ b/man/format_table.Rd @@ -0,0 +1,20 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/table.R +\name{format_table} +\alias{format_table} +\title{Format table as string} +\usage{ +format_table(tbl, format) +} +\arguments{ +\item{tbl}{A tibble to format} + +\item{format}{The output format} +} +\value{ +A character string +} +\description{ +Internal function to convert tibble to markdown, html, or latex string. +} +\keyword{internal} diff --git a/man/layer_bootstrap_density.Rd b/man/layer_bootstrap_density.Rd new file mode 100644 index 0000000..fd4ada2 --- /dev/null +++ b/man/layer_bootstrap_density.Rd @@ -0,0 +1,30 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/plot-layers.R +\name{layer_bootstrap_density} +\alias{layer_bootstrap_density} +\title{Create bootstrap density layer} +\usage{ +layer_bootstrap_density(data_view, config) +} +\arguments{ +\item{data_view}{A besthr_data_view object} + +\item{config}{A besthr_plot_config object} +} +\value{ +A list of ggplot2 layers +} +\description{ +Creates a ggplot2 layer showing ridge density plots of bootstrap distributions +with confidence interval shading. +} +\examples{ +\donttest{ +d <- make_data() +hr <- estimate(d, score, group) +dv <- besthr_data_view(hr) +cfg <- besthr_plot_config() +# layer_bootstrap_density returns layers to add to a ggplot +} + +} diff --git a/man/layer_group_means.Rd b/man/layer_group_means.Rd new file mode 100644 index 0000000..77fd9d3 --- /dev/null +++ b/man/layer_group_means.Rd @@ -0,0 +1,29 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/plot-layers.R +\name{layer_group_means} +\alias{layer_group_means} +\title{Create group mean lines layer} +\usage{ +layer_group_means(data_view, config) +} +\arguments{ +\item{data_view}{A besthr_data_view object} + +\item{config}{A besthr_plot_config object} +} +\value{ +A ggplot2 layer +} +\description{ +Creates a ggplot2 layer showing horizontal lines at group mean ranks. +} +\examples{ +\donttest{ +d <- make_data() +hr <- estimate(d, score, group) +dv <- besthr_data_view(hr) +cfg <- besthr_plot_config() +# layer_group_means returns a ggplot layer +} + +} diff --git a/man/layer_ranked_dots.Rd b/man/layer_ranked_dots.Rd new file mode 100644 index 0000000..ce47bf9 --- /dev/null +++ b/man/layer_ranked_dots.Rd @@ -0,0 +1,30 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/plot-layers.R +\name{layer_ranked_dots} +\alias{layer_ranked_dots} +\title{Create ranked dots layer} +\usage{ +layer_ranked_dots(data_view, config) +} +\arguments{ +\item{data_view}{A besthr_data_view object} + +\item{config}{A besthr_plot_config object} +} +\value{ +A list of ggplot2 layers +} +\description{ +Creates a ggplot2 layer showing ranked observations as points, where point +size indicates the count of observations at each rank/group combination. +} +\examples{ +\donttest{ +d <- make_data() +hr <- estimate(d, score, group) +dv <- besthr_data_view(hr) +cfg <- besthr_plot_config() +# layer_ranked_dots returns layers to add to a ggplot +} + +} diff --git a/man/layer_tech_rep_dots.Rd b/man/layer_tech_rep_dots.Rd new file mode 100644 index 0000000..679d8ef --- /dev/null +++ b/man/layer_tech_rep_dots.Rd @@ -0,0 +1,20 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/plot-layers.R +\name{layer_tech_rep_dots} +\alias{layer_tech_rep_dots} +\title{Create technical replicate dots layer} +\usage{ +layer_tech_rep_dots(data_view, config) +} +\arguments{ +\item{data_view}{A besthr_data_view object} + +\item{config}{A besthr_plot_config object} +} +\value{ +A list of ggplot2 layers +} +\description{ +Creates a ggplot2 layer showing raw score observations with technical +replicates displayed separately. Points are sized by observation count. +} diff --git a/man/list_besthr_styles.Rd b/man/list_besthr_styles.Rd new file mode 100644 index 0000000..30492d6 --- /dev/null +++ b/man/list_besthr_styles.Rd @@ -0,0 +1,18 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/themes.R +\name{list_besthr_styles} +\alias{list_besthr_styles} +\title{List available style presets} +\usage{ +list_besthr_styles() +} +\value{ +A character vector of style names (invisibly) +} +\description{ +Shows all available preset styles that can be used with \code{besthr_style()}. +} +\examples{ +list_besthr_styles() + +} diff --git a/man/plot.hrest.Rd b/man/plot.hrest.Rd index 37b02da..6bef10a 100644 --- a/man/plot.hrest.Rd +++ b/man/plot.hrest.Rd @@ -1,18 +1,41 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/functions.R +% Please edit documentation in R/plot-hrest.R \name{plot.hrest} \alias{plot.hrest} \title{plots the \code{hrest} object} \usage{ -\method{plot}{hrest}(x, ..., which = "rank_simulation") +\method{plot}{hrest}( + x, + ..., + which = "rank_simulation", + theme = "modern", + colors = "okabe_ito", + config = NULL, + show_significance = FALSE, + show_effect_size = FALSE +) } \arguments{ \item{x}{the \code{hrest} object from \code{\link{estimate}}} -\item{...}{Other parameters} +\item{...}{Other parameters (ignored)} \item{which}{the type of left hand panel to create. Either "rank_simulation" or "just_data"} + +\item{theme}{the visual theme to use. Either "modern" (default, cleaner +contemporary style) or "classic" (original besthr appearance)} + +\item{colors}{the color palette to use. Either "okabe_ito" (default, +colorblind-safe), "default" (original colors), or "viridis"} + +\item{config}{an optional besthr_plot_config object for advanced customization. +If provided, theme and colors parameters are ignored.} + +\item{show_significance}{Logical, whether to show significance stars on groups +where CI doesn't overlap control (default FALSE)} + +\item{show_effect_size}{Logical, whether to show effect size annotation (default FALSE)} } \value{ ggplot object @@ -34,4 +57,14 @@ control groups. hr_est <- estimate(d1, score, group) plot(hr_est) + # Use modern theme with colorblind-safe palette + plot(hr_est, theme = "modern", colors = "okabe_ito") + + # Advanced configuration + cfg <- besthr_plot_config( + panel_widths = c(2, 1), + point_size_range = c(3, 10) + ) + plot(hr_est, config = cfg) + } diff --git a/man/plot_bootstrap_raincloud.Rd b/man/plot_bootstrap_raincloud.Rd new file mode 100644 index 0000000..2bac2ae --- /dev/null +++ b/man/plot_bootstrap_raincloud.Rd @@ -0,0 +1,38 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/plot-raincloud.R +\name{plot_bootstrap_raincloud} +\alias{plot_bootstrap_raincloud} +\title{Raincloud plot showing bootstrap distributions} +\usage{ +plot_bootstrap_raincloud( + hrest, + theme = "modern", + colors = "okabe_ito", + config = NULL +) +} +\arguments{ +\item{hrest}{An hrest object from \code{\link{estimate}}} + +\item{theme}{the visual theme to use. Either "modern" (default) or "classic"} + +\item{colors}{the color palette to use. Either "okabe_ito" (default), "default", or "viridis"} + +\item{config}{an optional besthr_plot_config object} +} +\value{ +A ggplot object +} +\description{ +Creates a raincloud plot specifically for bootstrap distributions, showing +the distribution of bootstrap mean ranks with jittered points and summary +statistics. +} +\examples{ +\donttest{ +d <- make_data() +hr <- estimate(d, score, group, nits = 100) +plot_bootstrap_raincloud(hr) +} + +} diff --git a/man/plot_raincloud.Rd b/man/plot_raincloud.Rd new file mode 100644 index 0000000..391b7b2 --- /dev/null +++ b/man/plot_raincloud.Rd @@ -0,0 +1,55 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/plot-raincloud.R +\name{plot_raincloud} +\alias{plot_raincloud} +\title{Raincloud plot for hrest objects} +\usage{ +plot_raincloud( + hrest, + theme = "modern", + colors = "okabe_ito", + config = NULL, + show_bootstrap = TRUE, + jitter_width = 0.15, + point_size = 1.5 +) +} +\arguments{ +\item{hrest}{An hrest object from \code{\link{estimate}}} + +\item{theme}{the visual theme to use. Either "modern" (default) or "classic"} + +\item{colors}{the color palette to use. Either "okabe_ito" (default), "default", or "viridis"} + +\item{config}{an optional besthr_plot_config object for advanced customization. +If provided, theme and colors parameters are ignored.} + +\item{show_bootstrap}{Ignored (kept for backward compatibility).} + +\item{jitter_width}{Numeric width of jitter for data points. Default 0.15.} + +\item{point_size}{Numeric size for jittered points. Default 1.5.} +} +\value{ +A ggplot object +} +\description{ +Creates a unified raincloud visualization combining: +\itemize{ + \item Jittered raw data points + \item Half-violin density plots + \item Mean with confidence interval as pointrange +} +} +\details{ +This provides an alternative to the standard two-panel besthr plot, +combining all information in a single comprehensive visualization. +} +\examples{ +\donttest{ +d <- make_data() +hr <- estimate(d, score, group) +plot_raincloud(hr) +} + +} diff --git a/man/print.besthr_data_view.Rd b/man/print.besthr_data_view.Rd new file mode 100644 index 0000000..f523b85 --- /dev/null +++ b/man/print.besthr_data_view.Rd @@ -0,0 +1,19 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/plot-layers.R +\name{print.besthr_data_view} +\alias{print.besthr_data_view} +\title{Print method for besthr_data_view} +\usage{ +\method{print}{besthr_data_view}(x, ...) +} +\arguments{ +\item{x}{A besthr_data_view object} + +\item{...}{Additional arguments (ignored)} +} +\value{ +Invisibly returns x +} +\description{ +Print method for besthr_data_view +} diff --git a/man/print.besthr_plot_config.Rd b/man/print.besthr_plot_config.Rd new file mode 100644 index 0000000..498838a --- /dev/null +++ b/man/print.besthr_plot_config.Rd @@ -0,0 +1,19 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/plot-config.R +\name{print.besthr_plot_config} +\alias{print.besthr_plot_config} +\title{Print method for besthr_plot_config} +\usage{ +\method{print}{besthr_plot_config}(x, ...) +} +\arguments{ +\item{x}{A besthr_plot_config object} + +\item{...}{Additional arguments (ignored)} +} +\value{ +Invisibly returns x +} +\description{ +Print method for besthr_plot_config +} diff --git a/man/save_besthr.Rd b/man/save_besthr.Rd new file mode 100644 index 0000000..35b50ec --- /dev/null +++ b/man/save_besthr.Rd @@ -0,0 +1,48 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/export.R +\name{save_besthr} +\alias{save_besthr} +\title{Save besthr plot to file} +\usage{ +save_besthr( + hrest, + filename, + type = "default", + width = 8, + height = 6, + dpi = 300, + ... +) +} +\arguments{ +\item{hrest}{An hrest object from \code{\link{estimate}}} + +\item{filename}{Output filename. Format is detected from extension.} + +\item{type}{Plot type: "default" (two-panel) or "raincloud"} + +\item{width}{Plot width in inches (default 8)} + +\item{height}{Plot height in inches (default 6)} + +\item{dpi}{Resolution in dots per inch (default 300)} + +\item{...}{Additional arguments passed to the plot function (e.g., theme, colors)} +} +\value{ +The filename (invisibly) +} +\description{ +Saves a besthr visualization to a file with sensible publication defaults. +Supports PNG, PDF, SVG, and TIFF formats. +} +\examples{ +\dontrun{ +d <- make_data() +hr <- estimate(d, score, group) +save_besthr(hr, "figure1.png") +save_besthr(hr, "figure1.pdf", width = 10, height = 8) +save_besthr(hr, "figure1.png", type = "raincloud") +} + +} diff --git a/man/scale_color_besthr.Rd b/man/scale_color_besthr.Rd new file mode 100644 index 0000000..c94e5be --- /dev/null +++ b/man/scale_color_besthr.Rd @@ -0,0 +1,29 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/themes.R +\name{scale_color_besthr} +\alias{scale_color_besthr} +\alias{scale_colour_besthr} +\title{Discrete color scale for besthr} +\usage{ +scale_color_besthr(palette = "default", ...) + +scale_colour_besthr(palette = "default", ...) +} +\arguments{ +\item{palette}{Character string specifying the palette (see \code{\link{besthr_palette}})} + +\item{...}{Additional arguments passed to \code{\link[ggplot2]{discrete_scale}}} +} +\value{ +A ggplot2 discrete color scale +} +\description{ +A discrete color scale using besthr palettes. +} +\examples{ +library(ggplot2) +ggplot(mtcars, aes(mpg, wt, color = factor(cyl))) + + geom_point() + + scale_color_besthr("okabe_ito") + +} diff --git a/man/scale_fill_besthr.Rd b/man/scale_fill_besthr.Rd new file mode 100644 index 0000000..c93c239 --- /dev/null +++ b/man/scale_fill_besthr.Rd @@ -0,0 +1,26 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/themes.R +\name{scale_fill_besthr} +\alias{scale_fill_besthr} +\title{Discrete fill scale for besthr} +\usage{ +scale_fill_besthr(palette = "default", ...) +} +\arguments{ +\item{palette}{Character string specifying the palette (see \code{\link{besthr_palette}})} + +\item{...}{Additional arguments passed to \code{\link[ggplot2]{discrete_scale}}} +} +\value{ +A ggplot2 discrete fill scale +} +\description{ +A discrete fill scale using besthr palettes. +} +\examples{ +library(ggplot2) +ggplot(mtcars, aes(factor(cyl), fill = factor(cyl))) + + geom_bar() + + scale_fill_besthr("okabe_ito") + +} diff --git a/man/tech_rep_dot_plot.Rd b/man/tech_rep_dot_plot.Rd index d5e1254..a2f77af 100644 --- a/man/tech_rep_dot_plot.Rd +++ b/man/tech_rep_dot_plot.Rd @@ -1,10 +1,17 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/functions.R +% Please edit documentation in R/plot-hrest.R \name{tech_rep_dot_plot} \alias{tech_rep_dot_plot} \title{dot plot of score data with technical replicates} \usage{ -tech_rep_dot_plot(hrest, score_col, group_col, tech_rep_col) +tech_rep_dot_plot( + hrest, + score_col, + group_col, + tech_rep_col, + theme_style = "modern", + color_palette = "okabe_ito" +) } \arguments{ \item{hrest}{the hrest object from \code{estimate}} @@ -14,6 +21,10 @@ tech_rep_dot_plot(hrest, score_col, group_col, tech_rep_col) \item{group_col}{quoted group column name} \item{tech_rep_col}{quoted tech replicate column name} + +\item{theme_style}{character specifying the theme style} + +\item{color_palette}{character specifying the color palette} } \description{ \code{tech_rep_dot_plot} returns a ggplot object of score data with group on diff --git a/man/theme_besthr.Rd b/man/theme_besthr.Rd new file mode 100644 index 0000000..21da820 --- /dev/null +++ b/man/theme_besthr.Rd @@ -0,0 +1,33 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/themes.R +\name{theme_besthr} +\alias{theme_besthr} +\title{besthr ggplot2 theme} +\usage{ +theme_besthr(style = "classic", base_size = 11, base_family = "") +} +\arguments{ +\item{style}{Character string specifying the theme style. Options are: +\itemize{ + \item "classic" - Original besthr theme (theme_minimal) + \item "modern" - Clean, contemporary style with refined typography +}} + +\item{base_size}{Base font size (default 11)} + +\item{base_family}{Base font family} +} +\value{ +A ggplot2 theme object +} +\description{ +A custom theme for besthr plots. The "classic" theme matches the original +besthr appearance, while "modern" provides a cleaner, more contemporary look. +} +\examples{ +library(ggplot2) +ggplot(mtcars, aes(mpg, wt)) + + geom_point() + + theme_besthr("modern") + +} diff --git a/man/update_config.Rd b/man/update_config.Rd new file mode 100644 index 0000000..0c5c7d2 --- /dev/null +++ b/man/update_config.Rd @@ -0,0 +1,24 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/plot-config.R +\name{update_config} +\alias{update_config} +\title{Update a besthr plot configuration} +\usage{ +update_config(config, ...) +} +\arguments{ +\item{config}{An existing besthr_plot_config object} + +\item{...}{Named arguments to update} +} +\value{ +A new besthr_plot_config object +} +\description{ +Creates a new configuration by updating specific fields of an existing one. +} +\examples{ +cfg <- besthr_plot_config() +cfg2 <- update_config(cfg, theme_style = "modern", panel_widths = c(2, 1)) + +} diff --git a/renv.lock b/renv.lock index 48715ca..47ec23c 100644 --- a/renv.lock +++ b/renv.lock @@ -1,6 +1,6 @@ { "R": { - "Version": "4.2.0", + "Version": "4.5.1", "Repositories": [ { "Name": "CRAN", diff --git a/tests/testthat.R b/tests/testthat.R new file mode 100644 index 0000000..212dd02 --- /dev/null +++ b/tests/testthat.R @@ -0,0 +1,12 @@ +# This file is part of the standard setup for testthat. +# It is recommended that you do not modify it. +# +# Where should you do additional test configuration? +# Learn more about the roles of various files in: +# * https://r-pkgs.org/testing-design.html#sec-tests-files-overview +# * https://testthat.r-lib.org/articles/special-files.html + +library(testthat) +library(besthr) + +test_check("besthr") diff --git a/tests/testthat/test-effect-size.R b/tests/testthat/test-effect-size.R new file mode 100644 index 0000000..158df5a --- /dev/null +++ b/tests/testthat/test-effect-size.R @@ -0,0 +1,89 @@ +# Tests for effect size annotation feature + +test_that("effect size annotation appears when show_effect_size = TRUE", { + set.seed(123) + hr <- estimate(make_data(), score, group, control = "A", nits = 100) + p <- plot(hr, show_effect_size = TRUE) + + # Should have additional annotation or label + expect_s3_class(p, "patchwork") +}) + +test_that("effect size annotation hidden by default", { + set.seed(456) + hr <- estimate(make_data(), score, group, nits = 100) + p <- plot(hr) + + # Default plot should work without effect size + expect_s3_class(p, "patchwork") +}) + +test_that("compute_effect_size returns correct structure", { + set.seed(789) + hr <- estimate(make_data(), score, group, control = "A", nits = 200) + + effect <- compute_effect_size(hr) + + expect_s3_class(effect, "data.frame") + expect_true("group" %in% names(effect)) + expect_true("effect" %in% names(effect)) + expect_true("effect_ci_low" %in% names(effect)) + expect_true("effect_ci_high" %in% names(effect)) +}) + +test_that("effect size is NA for control group", { + set.seed(42) + hr <- estimate(make_data(), score, group, control = "A", nits = 100) + + effect <- compute_effect_size(hr) + + expect_true(is.na(effect$effect[effect$group == "A"])) +}) + +test_that("effect size calculation is mathematically correct", { + set.seed(111) + hr <- estimate(make_data(), score, group, control = "A", nits = 100) + + effect <- compute_effect_size(hr) + + # Effect for B should be mean(B) - mean(A) + control_mean <- hr$group_means$mean[hr$group_means$group == "A"] + treatment_mean <- hr$group_means$mean[hr$group_means$group == "B"] + expected_effect <- treatment_mean - control_mean + + expect_equal(effect$effect[effect$group == "B"], expected_effect) +}) + +test_that("effect size works with multiple groups", { + set.seed(222) + d <- make_data3() + hr <- estimate(d, score, sample, control = "A", nits = 100) + + effect <- compute_effect_size(hr) + + # Should have effect for B and C, NA for A + expect_true(is.na(effect$effect[effect$group == "A"])) + expect_false(is.na(effect$effect[effect$group == "B"])) + expect_false(is.na(effect$effect[effect$group == "C"])) +}) + +test_that("show_effect_size works with config parameter", { + set.seed(333) + hr <- estimate(make_data(), score, group, nits = 100) + cfg <- besthr_plot_config(theme_style = "modern", color_palette = "okabe_ito") + p <- plot(hr, config = cfg, show_effect_size = TRUE) + + expect_s3_class(p, "patchwork") +}) + +test_that("effect size CI is computed from bootstrap", { + set.seed(444) + hr <- estimate(make_data(), score, group, control = "A", nits = 500) + + effect <- compute_effect_size(hr) + + # CI should be numeric and properly ordered + b_row <- effect[effect$group == "B", ] + expect_true(b_row$effect_ci_low < b_row$effect) + expect_true(b_row$effect_ci_high > b_row$effect) +}) diff --git a/tests/testthat/test-estimate.R b/tests/testthat/test-estimate.R new file mode 100644 index 0000000..6d32c95 --- /dev/null +++ b/tests/testthat/test-estimate.R @@ -0,0 +1,94 @@ +test_that("estimate returns hrest object", { + d <- make_data() + hr <- estimate(d, score, group, nits = 10) + + expect_s3_class(hr, "hrest") +}) + +test_that("estimate returns expected structure", { + d <- make_data() + hr <- estimate(d, score, group, nits = 10) + + expect_true("control" %in% names(hr)) + expect_true("group_means" %in% names(hr)) + expect_true("ranked_data" %in% names(hr)) + expect_true("original_data" %in% names(hr)) + expect_true("bootstraps" %in% names(hr)) + expect_true("ci" %in% names(hr)) + expect_true("nits" %in% names(hr)) + expect_true("low" %in% names(hr)) + expect_true("high" %in% names(hr)) + expect_true("group_n" %in% names(hr)) + expect_true("column_info" %in% names(hr)) +}) + +test_that("estimate uses correct control group", { + d <- make_data() + hr <- estimate(d, score, group, control = "A", nits = 10) + + expect_equal(hr$control, "A") +}) + +test_that("estimate uses custom control group", { + d <- make_data() + hr <- estimate(d, score, group, control = "B", nits = 10) + + expect_equal(hr$control, "B") +}) + +test_that("estimate stores correct number of iterations", { + d <- make_data() + hr <- estimate(d, score, group, nits = 50) + + expect_equal(hr$nits, 50) +}) + +test_that("estimate stores correct confidence interval limits", { + d <- make_data() + hr <- estimate(d, score, group, nits = 10, low = 0.1, high = 0.9) + + expect_equal(hr$low, 0.1) + expect_equal(hr$high, 0.9) +}) + +test_that("estimate works with technical replicates (data2)", { + d <- make_data2() + hr <- estimate(d, score_column_name, sample_column_name, rep_column_name, nits = 10) + + expect_s3_class(hr, "hrest") + expect_equal(length(hr$column_info), 3) +}) + +test_that("estimate works with three groups (data3)", { + d <- make_data3() + hr <- estimate(d, score, sample, rep, nits = 10) + + expect_s3_class(hr, "hrest") + # Should have 2 non-control groups in CI + expect_equal(nrow(hr$ci), 2) +}) + +test_that("estimate ranked_data has rank column", { + d <- make_data() + hr <- estimate(d, score, group, nits = 10) + + expect_true("rank" %in% names(hr$ranked_data)) +}) + +test_that("estimate bootstraps has correct columns", { + d <- make_data() + hr <- estimate(d, score, group, nits = 10) + + expect_true("mean" %in% names(hr$bootstraps)) + expect_true("iteration" %in% names(hr$bootstraps)) + expect_true("group" %in% names(hr$bootstraps)) +}) + +test_that("estimate ci has correct columns", { + d <- make_data() + hr <- estimate(d, score, group, nits = 10) + + expect_true("low" %in% names(hr$ci)) + expect_true("high" %in% names(hr$ci)) + expect_true("mean" %in% names(hr$ci)) +}) diff --git a/tests/testthat/test-export.R b/tests/testthat/test-export.R new file mode 100644 index 0000000..6e4092f --- /dev/null +++ b/tests/testthat/test-export.R @@ -0,0 +1,130 @@ +# Tests for publication export feature + +test_that("save_besthr creates PNG file", { + set.seed(123) + hr <- estimate(make_data(), score, group, nits = 50) + tmp <- tempfile(fileext = ".png") + + save_besthr(hr, tmp) + + expect_true(file.exists(tmp)) + expect_gt(file.size(tmp), 0) + unlink(tmp) +}) + +test_that("save_besthr creates PDF file", { + set.seed(456) + hr <- estimate(make_data(), score, group, nits = 50) + tmp <- tempfile(fileext = ".pdf") + + save_besthr(hr, tmp) + + expect_true(file.exists(tmp)) + expect_gt(file.size(tmp), 0) + unlink(tmp) +}) + +test_that("save_besthr creates SVG file", { + skip_if_not_installed("svglite") + + set.seed(789) + hr <- estimate(make_data(), score, group, nits = 50) + tmp <- tempfile(fileext = ".svg") + + save_besthr(hr, tmp) + + expect_true(file.exists(tmp)) + expect_gt(file.size(tmp), 0) + unlink(tmp) +}) + +test_that("save_besthr creates TIFF file", { + set.seed(42) + hr <- estimate(make_data(), score, group, nits = 50) + tmp <- tempfile(fileext = ".tiff") + + save_besthr(hr, tmp) + + expect_true(file.exists(tmp)) + expect_gt(file.size(tmp), 0) + unlink(tmp) +}) + +test_that("save_besthr respects width and height", { + set.seed(111) + hr <- estimate(make_data(), score, group, nits = 50) + tmp <- tempfile(fileext = ".png") + + save_besthr(hr, tmp, width = 10, height = 8) + + expect_true(file.exists(tmp)) + unlink(tmp) +}) + +test_that("save_besthr respects dpi parameter", { + set.seed(222) + hr <- estimate(make_data(), score, group, nits = 50) + tmp_low <- tempfile(fileext = ".png") + tmp_high <- tempfile(fileext = ".png") + + save_besthr(hr, tmp_low, dpi = 72) + save_besthr(hr, tmp_high, dpi = 300) + + # Higher DPI should result in larger file + expect_gt(file.size(tmp_high), file.size(tmp_low)) + + unlink(tmp_low) + unlink(tmp_high) +}) + +test_that("save_besthr uses sensible defaults", { + set.seed(333) + hr <- estimate(make_data(), score, group, nits = 50) + tmp <- tempfile(fileext = ".png") + + # Should work with just filename + save_besthr(hr, tmp) + + expect_true(file.exists(tmp)) + unlink(tmp) +}) + +test_that("save_besthr can save raincloud plot", { + set.seed(555) + hr <- estimate(make_data(), score, group, nits = 50) + tmp <- tempfile(fileext = ".png") + + save_besthr(hr, tmp, type = "raincloud") + + expect_true(file.exists(tmp)) + unlink(tmp) +}) + +test_that("save_besthr passes plot options", { + set.seed(666) + hr <- estimate(make_data(), score, group, nits = 50) + tmp <- tempfile(fileext = ".png") + + save_besthr(hr, tmp, theme = "modern", colors = "okabe_ito") + + expect_true(file.exists(tmp)) + unlink(tmp) +}) + +test_that("save_besthr errors on invalid format", { + set.seed(777) + hr <- estimate(make_data(), score, group, nits = 50) + + expect_error(save_besthr(hr, "test.xyz")) +}) + +test_that("save_besthr returns filename invisibly", { + set.seed(888) + hr <- estimate(make_data(), score, group, nits = 50) + tmp <- tempfile(fileext = ".png") + + result <- save_besthr(hr, tmp) + + expect_equal(result, tmp) + unlink(tmp) +}) diff --git a/tests/testthat/test-make_data.R b/tests/testthat/test-make_data.R new file mode 100644 index 0000000..e53d381 --- /dev/null +++ b/tests/testthat/test-make_data.R @@ -0,0 +1,57 @@ +test_that("make_data returns correct structure", { + d <- make_data() + + expect_s3_class(d, "tbl_df") + expect_equal(nrow(d), 20) + expect_equal(ncol(d), 2) + expect_named(d, c("score", "group")) +}) + +test_that("make_data returns correct groups", { + d <- make_data() + + expect_equal(unique(d$group), c("A", "B")) + expect_equal(sum(d$group == "A"), 10) + expect_equal(sum(d$group == "B"), 10) +}) + +test_that("make_data score values are in expected range", { + d <- make_data() + + expect_true(all(d$score >= 1)) + expect_true(all(d$score <= 10)) +}) + +test_that("make_data2 returns correct structure with tech reps", { + d <- make_data2() + + expect_s3_class(d, "tbl_df") + expect_equal(nrow(d), 24) + expect_equal(ncol(d), 3) + expect_named(d, c("score_column_name", "sample_column_name", "rep_column_name")) +}) + +test_that("make_data2 returns correct groups and reps", { + d <- make_data2() + + expect_equal(unique(d$sample_column_name), c("A", "B")) + expect_equal(unique(d$rep_column_name), c(1, 2, 3)) +}) + +test_that("make_data3 returns correct structure with three groups", { + d <- make_data3() + + expect_s3_class(d, "tbl_df") + expect_equal(nrow(d), 36) + expect_equal(ncol(d), 3) + expect_named(d, c("score", "sample", "rep")) +}) + +test_that("make_data3 returns three groups", { + d <- make_data3() + + expect_equal(unique(d$sample), c("A", "B", "C")) + expect_equal(sum(d$sample == "A"), 12) + expect_equal(sum(d$sample == "B"), 12) + expect_equal(sum(d$sample == "C"), 12) +}) diff --git a/tests/testthat/test-plot-config.R b/tests/testthat/test-plot-config.R new file mode 100644 index 0000000..2dac3a2 --- /dev/null +++ b/tests/testthat/test-plot-config.R @@ -0,0 +1,183 @@ +test_that("besthr_plot_config creates valid config object", { + cfg <- besthr_plot_config() + + expect_s3_class(cfg, "besthr_plot_config") + expect_equal(cfg$panel_widths, c(1, 1)) + expect_equal(cfg$theme_style, "modern") + expect_equal(cfg$color_palette, "okabe_ito") +}) + +test_that("besthr_plot_config accepts custom parameters", { + cfg <- besthr_plot_config( + panel_widths = c(2, 1), + point_size_range = c(3, 10), + theme_style = "modern", + color_palette = "okabe_ito" + ) + + expect_equal(cfg$panel_widths, c(2, 1)) + expect_equal(cfg$point_size_range, c(3, 10)) + expect_equal(cfg$theme_style, "modern") + expect_equal(cfg$color_palette, "okabe_ito") +}) + +test_that("besthr_plot_config validates inputs", { + expect_error(besthr_plot_config(panel_widths = "invalid")) + expect_error(besthr_plot_config(y_limits = c(1, 2, 3))) + expect_error(besthr_plot_config(y_expand = -1)) + expect_error(besthr_plot_config(point_alpha = 2)) + expect_error(besthr_plot_config(theme_style = "nonexistent")) + expect_error(besthr_plot_config(color_palette = "nonexistent")) +}) + +test_that("update_config modifies config correctly", { + cfg <- besthr_plot_config() + cfg2 <- update_config(cfg, theme_style = "classic", panel_widths = c(2, 1)) + + expect_equal(cfg2$theme_style, "classic") + expect_equal(cfg2$panel_widths, c(2, 1)) + # Original should be unchanged + expect_equal(cfg$theme_style, "modern") +}) + +test_that("update_config validates input", { + cfg <- besthr_plot_config() + expect_error(update_config(list(), theme_style = "modern")) + expect_error(update_config(cfg, nonexistent_param = "value")) +}) + +test_that("print.besthr_plot_config works", { + cfg <- besthr_plot_config() + expect_output(print(cfg), "besthr plot configuration") + expect_output(print(cfg), "Theme: modern") +}) + +test_that("besthr_data_view creates valid view", { + d <- make_data() + hr <- estimate(d, score, group, nits = 10) + dv <- besthr_data_view(hr) + + expect_s3_class(dv, "besthr_data_view") + expect_true("ranked" %in% names(dv)) + expect_true("bootstrap" %in% names(dv)) + expect_true("rank_limits" %in% names(dv)) + expect_length(dv$rank_limits, 2) +}) + +test_that("besthr_data_view computes unified limits", { + d <- make_data() + hr <- estimate(d, score, group, nits = 10) + dv <- besthr_data_view(hr) + + # Limits should encompass all data + expect_true(dv$rank_limits[1] <= min(dv$ranked$rank)) + expect_true(dv$rank_limits[2] >= max(dv$ranked$rank)) + expect_true(dv$rank_limits[1] <= min(dv$bootstrap$mean)) + expect_true(dv$rank_limits[2] >= max(dv$bootstrap$mean)) +}) + +test_that("besthr_data_view respects custom y_limits", { + d <- make_data() + hr <- estimate(d, score, group, nits = 10) + cfg <- besthr_plot_config(y_limits = c(0, 100)) + dv <- besthr_data_view(hr, cfg) + + expect_equal(dv$rank_limits, c(0, 100)) +}) + +test_that("print.besthr_data_view works", { + d <- make_data() + hr <- estimate(d, score, group, nits = 10) + dv <- besthr_data_view(hr) + + expect_output(print(dv), "besthr data view") + expect_output(print(dv), "Groups:") +}) + +test_that("build_observation_panel returns ggplot", { + d <- make_data() + hr <- estimate(d, score, group, nits = 10) + dv <- besthr_data_view(hr) + cfg <- besthr_plot_config() + + p <- build_observation_panel(dv, cfg, "rank_simulation") + expect_s3_class(p, "ggplot") +}) + +test_that("build_bootstrap_panel returns ggplot", { + d <- make_data() + hr <- estimate(d, score, group, nits = 10) + dv <- besthr_data_view(hr) + cfg <- besthr_plot_config() + + p <- build_bootstrap_panel(dv, cfg) + expect_s3_class(p, "ggplot") +}) + +test_that("compose_besthr_panels creates patchwork", { + d <- make_data() + hr <- estimate(d, score, group, nits = 10) + dv <- besthr_data_view(hr) + cfg <- besthr_plot_config() + + p1 <- build_observation_panel(dv, cfg) + p2 <- build_bootstrap_panel(dv, cfg) + p <- compose_besthr_panels(list(p1, p2), cfg) + + expect_s3_class(p, "patchwork") +}) + +test_that("derive_ci_colors returns three colors", { + expect_length(derive_ci_colors("default", "classic"), 3) + expect_length(derive_ci_colors("okabe_ito", "classic"), 3) + expect_length(derive_ci_colors("viridis", "modern"), 3) +}) + +test_that("derive_ci_colors returns hex colors", { + colors <- derive_ci_colors("okabe_ito", "classic") + expect_true(all(grepl("^#", colors))) +}) + +test_that("besthr_plot_config supports density_style option", { + cfg <- besthr_plot_config(density_style = "points") + expect_equal(cfg$density_style, "points") + + cfg2 <- besthr_plot_config(density_style = "solid") + expect_equal(cfg2$density_style, "solid") + + cfg3 <- besthr_plot_config(density_style = "gradient") + expect_equal(cfg3$density_style, "gradient") + + expect_error(besthr_plot_config(density_style = "invalid")) +}) + +test_that("besthr_style returns valid configs", { + expect_s3_class(besthr_style("default"), "besthr_plot_config") + expect_s3_class(besthr_style("classic"), "besthr_plot_config") + expect_s3_class(besthr_style("publication"), "besthr_plot_config") + expect_s3_class(besthr_style("presentation"), "besthr_plot_config") + expect_s3_class(besthr_style("density"), "besthr_plot_config") +}) + +test_that("besthr_style presets have expected settings", { + default <- besthr_style("default") + expect_equal(default$theme_style, "modern") + expect_equal(default$color_palette, "okabe_ito") + + classic <- besthr_style("classic") + expect_equal(classic$theme_style, "classic") + expect_equal(classic$color_palette, "default") + + density <- besthr_style("density") + expect_equal(density$density_style, "gradient") +}) + +test_that("besthr_style validates input", { + expect_error(besthr_style("nonexistent")) +}) + +test_that("list_besthr_styles outputs style list", { + expect_output(list_besthr_styles(), "default") + expect_output(list_besthr_styles(), "classic") + expect_output(list_besthr_styles(), "publication") +}) diff --git a/tests/testthat/test-plot.R b/tests/testthat/test-plot.R new file mode 100644 index 0000000..fecd34b --- /dev/null +++ b/tests/testthat/test-plot.R @@ -0,0 +1,64 @@ +test_that("plot.hrest returns a ggplot object", { + d <- make_data() + hr <- estimate(d, score, group, nits = 10) + p <- plot(hr) + + expect_s3_class(p, "ggplot") +}) + +test_that("plot.hrest works with rank_simulation option", { + d <- make_data() + hr <- estimate(d, score, group, nits = 10) + p <- plot(hr, which = "rank_simulation") + + expect_s3_class(p, "ggplot") +}) + +test_that("plot.hrest works with just_data option and tech reps", { + d <- make_data2() + hr <- estimate(d, score_column_name, sample_column_name, rep_column_name, nits = 10) + p <- plot(hr, which = "just_data") + + expect_s3_class(p, "ggplot") +}) + +test_that("plot.hrest works with three groups", { + d <- make_data3() + hr <- estimate(d, score, sample, rep, nits = 10) + p <- plot(hr) + + expect_s3_class(p, "ggplot") +}) + +test_that("plot.hrest uses patchwork for layout", { + d <- make_data() + hr <- estimate(d, score, group, nits = 10) + p <- plot(hr) + + # patchwork objects are both patchwork and ggplot + expect_s3_class(p, "patchwork") +}) + +test_that("plot.hrest works with config parameter", { + d <- make_data() + hr <- estimate(d, score, group, nits = 10) + cfg <- besthr_plot_config( + panel_widths = c(2, 1), + theme_style = "modern", + color_palette = "okabe_ito" + ) + p <- plot(hr, config = cfg) + + expect_s3_class(p, "ggplot") + expect_s3_class(p, "patchwork") +}) + +test_that("plot.hrest config overrides theme and colors params", { + d <- make_data() + hr <- estimate(d, score, group, nits = 10) + cfg <- besthr_plot_config(theme_style = "modern", color_palette = "okabe_ito") + + # When config is provided, theme and colors params are ignored + p <- plot(hr, theme = "classic", colors = "default", config = cfg) + expect_s3_class(p, "ggplot") +}) diff --git a/tests/testthat/test-print.R b/tests/testthat/test-print.R new file mode 100644 index 0000000..b2b9db5 --- /dev/null +++ b/tests/testthat/test-print.R @@ -0,0 +1,34 @@ +test_that("print.hrest produces output", { + d <- make_data() + hr <- estimate(d, score, group, nits = 10) + + expect_output(print(hr), "besthr") +}) + +test_that("print.hrest shows control group", { + d <- make_data() + hr <- estimate(d, score, group, control = "A", nits = 10) + + expect_output(print(hr), "Control: A") +}) + +test_that("print.hrest shows confidence intervals", { + d <- make_data() + hr <- estimate(d, score, group, nits = 10) + + expect_output(print(hr), "Confidence Intervals") +}) + +test_that("print.hrest shows bootstrap count", { + d <- make_data() + hr <- estimate(d, score, group, nits = 25) + + expect_output(print(hr), "25 bootstrap resamples") +}) + +test_that("print.hrest returns NULL invisibly", { + d <- make_data() + hr <- estimate(d, score, group, nits = 10) + + expect_invisible(print(hr)) +}) diff --git a/tests/testthat/test-raincloud.R b/tests/testthat/test-raincloud.R new file mode 100644 index 0000000..41c4594 --- /dev/null +++ b/tests/testthat/test-raincloud.R @@ -0,0 +1,83 @@ +test_that("plot_raincloud returns ggplot", { + d <- make_data() + hr <- estimate(d, score, group, nits = 10) + p <- plot_raincloud(hr) + + expect_s3_class(p, "ggplot") +}) + +test_that("plot_raincloud works with different themes", { + d <- make_data() + hr <- estimate(d, score, group, nits = 10) + + p_classic <- plot_raincloud(hr, theme = "classic") + p_modern <- plot_raincloud(hr, theme = "modern") + + expect_s3_class(p_classic, "ggplot") + expect_s3_class(p_modern, "ggplot") +}) + +test_that("plot_raincloud works with different color palettes", { + d <- make_data() + hr <- estimate(d, score, group, nits = 10) + + p_default <- plot_raincloud(hr, colors = "default") + p_okabe <- plot_raincloud(hr, colors = "okabe_ito") + p_viridis <- plot_raincloud(hr, colors = "viridis") + + expect_s3_class(p_default, "ggplot") + expect_s3_class(p_okabe, "ggplot") + expect_s3_class(p_viridis, "ggplot") +}) + +test_that("plot_raincloud accepts config object", { + d <- make_data() + hr <- estimate(d, score, group, nits = 10) + cfg <- besthr_plot_config(theme_style = "modern", color_palette = "okabe_ito") + + p <- plot_raincloud(hr, config = cfg) + expect_s3_class(p, "ggplot") +}) + +test_that("plot_raincloud works without bootstrap", { + d <- make_data() + hr <- estimate(d, score, group, nits = 10) + p <- plot_raincloud(hr, show_bootstrap = FALSE) + + expect_s3_class(p, "ggplot") +}) + +test_that("plot_raincloud works with three groups", { + d <- make_data3() + hr <- estimate(d, score, sample, rep, nits = 10) + p <- plot_raincloud(hr) + + expect_s3_class(p, "ggplot") +}) + +test_that("plot_bootstrap_raincloud returns ggplot", { + d <- make_data() + hr <- estimate(d, score, group, nits = 10) + p <- plot_bootstrap_raincloud(hr) + + expect_s3_class(p, "ggplot") +}) + +test_that("plot_bootstrap_raincloud works with different themes", { + d <- make_data() + hr <- estimate(d, score, group, nits = 10) + + p_classic <- plot_bootstrap_raincloud(hr, theme = "classic") + p_modern <- plot_bootstrap_raincloud(hr, theme = "modern") + + expect_s3_class(p_classic, "ggplot") + expect_s3_class(p_modern, "ggplot") +}) + +test_that("plot_raincloud errors on non-hrest input", { + expect_error(plot_raincloud(list()), "hrest must be an hrest object") +}) + +test_that("plot_bootstrap_raincloud errors on non-hrest input", { + expect_error(plot_bootstrap_raincloud(list()), "hrest must be an hrest object") +}) diff --git a/tests/testthat/test-significance.R b/tests/testthat/test-significance.R new file mode 100644 index 0000000..f3f2866 --- /dev/null +++ b/tests/testthat/test-significance.R @@ -0,0 +1,100 @@ +# Tests for significance annotations feature + +test_that("significance annotations appear when show_significance = TRUE", { + set.seed(123) + d <- make_data() + hr <- estimate(d, score, group, nits = 200) + p <- plot(hr, show_significance = TRUE) + + + # Check that text/annotation layer exists in observation panel + obs_panel <- p[[1]] + has_text <- any(sapply(obs_panel$layers, function(l) { + inherits(l$geom, "GeomText") || inherits(l$geom, "GeomLabel") + })) + expect_true(has_text) +}) + +test_that("significance annotations hidden by default", { + set.seed(456) + hr <- estimate(make_data(), score, group, nits = 100) + p <- plot(hr) + + # No text annotations by default in observation panel + obs_panel <- p[[1]] + has_text <- any(sapply(obs_panel$layers, function(l) { + inherits(l$geom, "GeomText") || inherits(l$geom, "GeomLabel") + })) + expect_false(has_text) +}) + +test_that("compute_significance returns correct structure", { + set.seed(789) + hr <- estimate(make_data(), score, group, control = "A", nits = 200) + + sig <- compute_significance(hr) + + expect_s3_class(sig, "data.frame") + expect_true("group" %in% names(sig)) + expect_true("significant" %in% names(sig)) + expect_true("stars" %in% names(sig)) +}) + +test_that("significance correctly identifies non-overlapping CI", { + # Create data with clear separation + set.seed(42) + d <- data.frame( + score = c(rep(2, 10), rep(8, 10)), + group = rep(c("A", "B"), each = 10) + ) + hr <- estimate(d, score, group, control = "A", nits = 500) + + sig <- compute_significance(hr) + + # B should be significantly different from A + expect_true(sig$significant[sig$group == "B"]) +}) + +test_that("control group is not marked as significant", { + set.seed(111) + hr <- estimate(make_data(), score, group, control = "A", nits = 100) + + sig <- compute_significance(hr) + + # Control group should have NA or FALSE for significance + expect_true(is.na(sig$significant[sig$group == "A"]) || + !sig$significant[sig$group == "A"]) +}) + +test_that("significance stars reflect p-value thresholds", { + # * for p < 0.05, ** for p < 0.01, *** for p < 0.001 + set.seed(222) + d <- data.frame( + score = c(rep(1, 10), rep(10, 10)), + group = rep(c("A", "B"), each = 10) + ) + hr <- estimate(d, score, group, control = "A", nits = 1000) + + sig <- compute_significance(hr) + + # With clear separation, should have at least one star + expect_true(nchar(sig$stars[sig$group == "B"]) >= 1) +}) + +test_that("show_significance works with multiple groups", { + set.seed(333) + d <- make_data3() + hr <- estimate(d, score, sample, control = "A", nits = 100) + p <- plot(hr, show_significance = TRUE) + + expect_s3_class(p, "patchwork") +}) + +test_that("show_significance works with config parameter", { + set.seed(444) + hr <- estimate(make_data(), score, group, nits = 100) + cfg <- besthr_plot_config(theme_style = "modern") + p <- plot(hr, config = cfg, show_significance = TRUE) + + expect_s3_class(p, "patchwork") +}) diff --git a/tests/testthat/test-table.R b/tests/testthat/test-table.R new file mode 100644 index 0000000..b232bb2 --- /dev/null +++ b/tests/testthat/test-table.R @@ -0,0 +1,149 @@ +# Tests for summary table feature + +test_that("besthr_table returns tibble with required columns", { + set.seed(123) + hr <- estimate(make_data(), score, group, nits = 100) + tbl <- besthr_table(hr) + + expect_s3_class(tbl, "tbl_df") + expect_true("group" %in% names(tbl)) + expect_true("n" %in% names(tbl)) + expect_true("mean_rank" %in% names(tbl)) + expect_true("ci_low" %in% names(tbl)) + expect_true("ci_high" %in% names(tbl)) +}) + +test_that("besthr_table includes effect size column", { + set.seed(456) + hr <- estimate(make_data(), score, group, control = "A", nits = 100) + tbl <- besthr_table(hr) + + expect_true("effect_size" %in% names(tbl)) +}) + +test_that("besthr_table effect size is NA for control", { + set.seed(789) + hr <- estimate(make_data(), score, group, control = "A", nits = 100) + tbl <- besthr_table(hr) + + expect_true(is.na(tbl$effect_size[tbl$group == "A"])) +}) + +test_that("besthr_table effect size is computed for treatment", { + set.seed(42) + hr <- estimate(make_data(), score, group, control = "A", nits = 100) + tbl <- besthr_table(hr) + + expect_false(is.na(tbl$effect_size[tbl$group == "B"])) +}) + +test_that("besthr_table n values match hrest object", { + set.seed(111) + hr <- estimate(make_data(), score, group, nits = 100) + tbl <- besthr_table(hr) + + for (g in unique(tbl$group)) { + expected_n <- hr$group_n$n[hr$group_n$group == g] + actual_n <- tbl$n[tbl$group == g] + expect_equal(actual_n, expected_n) + } +}) + +test_that("besthr_table mean_rank values match hrest object", { + set.seed(222) + hr <- estimate(make_data(), score, group, nits = 100) + tbl <- besthr_table(hr) + + for (g in unique(tbl$group)) { + expected_mean <- hr$group_means$mean[hr$group_means$group == g] + actual_mean <- tbl$mean_rank[tbl$group == g] + expect_equal(actual_mean, expected_mean) + } +}) + +test_that("besthr_table CI values match hrest object", { + set.seed(333) + hr <- estimate(make_data(), score, group, control = "A", nits = 100) + tbl <- besthr_table(hr) + + # Check treatment group CI (table rounds to 2 decimal places by default) + b_ci <- hr$ci[hr$ci$group == "B", ] + expect_equal(tbl$ci_low[tbl$group == "B"], round(b_ci$low, 2)) + expect_equal(tbl$ci_high[tbl$group == "B"], round(b_ci$high, 2)) +}) + +test_that("besthr_table works with multiple groups", { + set.seed(444) + d <- make_data3() + hr <- estimate(d, score, sample, control = "A", nits = 100) + tbl <- besthr_table(hr) + + expect_equal(nrow(tbl), 3) + expect_true(all(c("A", "B", "C") %in% tbl$group)) +}) + +test_that("besthr_table markdown format is valid", { + set.seed(555) + hr <- estimate(make_data(), score, group, nits = 100) + md <- besthr_table(hr, format = "markdown") + + expect_true(is.character(md)) + expect_true(grepl("\\|", md)) # Contains pipe characters + expect_true(grepl("---", md)) # Contains header separator +}) + +test_that("besthr_table html format is valid", { + set.seed(666) + hr <- estimate(make_data(), score, group, nits = 100) + html <- besthr_table(hr, format = "html") + + expect_true(is.character(html)) + expect_true(grepl("% +hr_est_3 %>% plot(which = "just_data") ``` +## Built-in Themes and Color Palettes + +besthr uses a modern, colorblind-safe appearance by default. You can customize the look using themes and color palettes. + +### Theme Options + +Use the `theme` parameter to change the overall visual style: + +- `"modern"` (default) - A clean, contemporary style with refined typography +- `"classic"` - The original besthr appearance (for backward compatibility) + +```{r, fig.width=8, fig.height=6} +# Modern theme (default) +plot(hr_est_1) + +# Classic theme (original style) +plot(hr_est_1, theme = "classic") +``` + +### Color Palette Options + +Use the `colors` parameter to change the color palette: + +- `"okabe_ito"` (default) - Colorblind-safe Okabe-Ito palette +- `"default"` - Original besthr colors (for backward compatibility) +- `"viridis"` - Viridis color scale + +```{r, fig.width=8, fig.height=6} +# Default is already colorblind-safe +plot(hr_est_1) + +# Original colors +plot(hr_est_1, colors = "default") + +# Viridis palette +plot(hr_est_1, colors = "viridis") +``` + +### Quick Style Presets + +For easy customization without understanding all options, use preset styles: + +```{r, fig.width=8, fig.height=6} +# Publication-ready style +plot(hr_est_1, config = besthr_style("publication")) + +# Presentation style (larger elements) +plot(hr_est_1, config = besthr_style("presentation")) + +# Density style (gradient density instead of points) +plot(hr_est_1, config = besthr_style("density")) + +# See all available styles +list_besthr_styles() +``` + +### Using besthr Palettes Directly + +The color palettes can also be used directly in your own ggplot2 code: +```{r} +# Get palette colors +besthr_palette("okabe_ito", n = 4) + +# Available palettes +besthr_palette("default", n = 3) +besthr_palette("viridis", n = 3) +``` + ## Styling Plots -You can style plots to your own taste. The object returned from `plot()` is a `patchwork` [https://patchwork.data-imaginist.com/](https://patchwork.data-imaginist.com/) object that composes two separate plots, the dot plot and the bootstrap percentile plot, which are themselves `ggplot` objects. So you can use a mixture of `patchwork` annotations functions for whole plot labels and `ggplot` themes for individual elements. +### Using Built-in Themes and Colors + +The easiest way to style your plots is using the `theme` and `colors` parameters: -### Adding annotations. +```{r, fig.width=8, fig.height=6} +# Modern look with colorblind-safe colors (this is the default) +plot(hr_est_1, theme = "modern", colors = "okabe_ito") -You can use the `patchwork` `plot_annotation()` function to add titles +# Classic appearance (original besthr style) +plot(hr_est_1, theme = "classic", colors = "default") + +# Viridis color scheme +plot(hr_est_1, colors = "viridis") +``` + +### Adding Titles and Annotations + +The plot object is a `patchwork` composition. You can add titles using `plot_annotation()`: ```{r, fig.width=8, fig.height=6} library(patchwork) p <- plot(hr_est_1) -p + plot_annotation(title = 'A stylish besthr plot', - subtitle = "better than ever", - caption = 'Though this example is not meaningful') -p +p + plot_annotation( + title = 'HR Score Analysis', + subtitle = "Control vs Treatment", + caption = 'Generated with besthr' +) +``` + +## Advanced Configuration +For users who need fine-grained control over plot appearance, besthr provides a configuration system. -``` +### Using besthr_plot_config +The `besthr_plot_config()` function creates a configuration object that controls all aspects of plot appearance: -### Targetting a subplot to make theme changes +```{r} +# Create a custom configuration +cfg <- besthr_plot_config( + panel_widths = c(2, 1), # Make data panel wider than bootstrap panel + point_size_range = c(3, 10), # Larger points + point_alpha = 0.9, # More opaque points + mean_line_width = 1.5, # Thicker mean lines + theme_style = "modern", + color_palette = "okabe_ito" +) -You can change the style of the individual plot elements using subsetting syntax `[[]]` . The dot plot can be addressed within the `patchwork` object using index 1 within the `patchwork` object `p[[1]]`, and the percentile plot using `p[[2]]`. You must add to the existing subplot then assign the result back to see the difference in the plot. Here's an example that uses `theme()` to restyle the y-axis text of the dot plot +print(cfg) +``` ```{r, fig.width=8, fig.height=6} -library(ggplot2) -p[[1]] <- p[[1]] + theme(axis.title.y = element_text(family = "Times", colour="blue", size=24)) -p +# Use the configuration in plot +plot(hr_est_1, config = cfg) ``` +You can update an existing configuration with `update_config()`: -### Changing the scale colours of a subplot +```{r, fig.width=8, fig.height=6} +# Modify just one setting +cfg2 <- update_config(cfg, panel_widths = c(1, 2)) +plot(hr_est_1, config = cfg2) +``` -You can change the colours used by the scales in the same way using the `scale` functions, though as the type of scale is different for the dot plot and bootstrap plot you will need to apply a different scale for each. +### Configuration Options -For the dot plot, use a discrete scale e.g `scale_colour_manual()`, `scale_colour_viridis_d()` or `scale_colour_brewer(type = "qual")` +| Parameter | Default | Description | +|-----------|---------|-------------| +| `panel_widths` | `c(1, 1)` | Relative widths of observation and bootstrap panels | +| `y_limits` | `NULL` (auto) | Fixed y-axis limits, or NULL for automatic | +| `y_expand` | `0.05` | Proportional expansion of y-axis limits | +| `point_size_range` | `c(2, 8)` | Min/max point sizes based on count | +| `point_alpha` | `0.8` | Point transparency (0-1) | +| `mean_line_type` | `3` | Line type for mean indicators | +| `mean_line_width` | `1` | Line width for mean indicators | +| `density_alpha` | `0.7` | Bootstrap density transparency | +| `density_style` | `"points"` | Density display: "points" (jittered bootstrap), "gradient", or "solid" | +| `theme_style` | `"modern"` | Theme: "classic" or "modern" | +| `color_palette` | `"okabe_ito"` | Colors: "default", "okabe_ito", or "viridis" | -```{r, fig.width=8, fig.height=6} -p[[1]] <- p[[1]] + scale_colour_manual(values = c("blue", "#440000")) -p +## Building Custom Plots + +For maximum flexibility, you can build plots from individual components. + +### Using besthr_data_view + +The `besthr_data_view()` function extracts all plotting data from an hrest object with unified axis limits: + +```{r} +# Create a data view +dv <- besthr_data_view(hr_est_1) +print(dv) + +# Access the data +head(dv$ranked) +head(dv$bootstrap) +dv$rank_limits +``` -p[[1]] <- p[[1]] + scale_colour_viridis_d() -p +### Using Panel Builders -p[[1]] <- p[[1]] + scale_colour_brewer(type="qual", palette="Accent") -p +You can build individual panels and compose them yourself: + +```{r, fig.width=10, fig.height=5} +library(patchwork) + +# Create panels +cfg <- besthr_plot_config(theme_style = "modern", color_palette = "okabe_ito") +dv <- besthr_data_view(hr_est_1, cfg) + +p1 <- build_observation_panel(dv, cfg, "rank_simulation") +p2 <- build_bootstrap_panel(dv, cfg) + +# Custom composition with different widths +p1 + p2 + plot_layout(widths = c(3, 1)) +``` + +## Alternative Visualizations + +### Raincloud Plots + +The `plot_raincloud()` function provides an alternative visualization showing jittered data points with confidence intervals: + +```{r, fig.width=6, fig.height=5} +plot_raincloud(hr_est_1) +``` + +```{r, fig.width=6, fig.height=5} +# With modern styling +plot_raincloud(hr_est_1, theme = "modern", colors = "okabe_ito") +``` + +### Bootstrap Raincloud + +The `plot_bootstrap_raincloud()` function shows the bootstrap distribution as jittered points: + +```{r, fig.width=6, fig.height=5} +plot_bootstrap_raincloud(hr_est_1) +``` + +## CI Color Customization + +The `derive_ci_colors()` function generates confidence interval colors that harmonize with the selected palette: + +```{r} +# Default CI colors +derive_ci_colors("default", "classic") + +# Okabe-Ito derived CI colors +derive_ci_colors("okabe_ito", "modern") + +# Viridis derived CI colors +derive_ci_colors("viridis", "classic") +``` + + +## Significance and Effect Size + +```{r} +# Create example data with 3 groups and realistic variation +set.seed(42) +d_effect <- data.frame( + score = c( + sample(1:4, 12, replace = TRUE), # Group A: low scores (control) + sample(4:8, 12, replace = TRUE), # Group B: medium-high scores + sample(6:10, 12, replace = TRUE) # Group C: high scores + ), + group = rep(c("A", "B", "C"), each = 12) +) +hr_effect <- estimate(d_effect, score, group, control = "A", nits = 1000) ``` -For the percentile plot, use only `scale_colour_manual()` with specified colours. Annoyingly, this rewrites the other values associated with the scale each time, so you'll need to replace those. +### Significance Annotations + +Add significance stars to groups where the bootstrap confidence interval does not overlap the control mean: + +```{r, fig.width=8, fig.height=6} +plot(hr_effect, show_significance = TRUE) +``` + +### Effect Size Annotation + +Display effect size (difference from control) with confidence intervals: ```{r, fig.width=8, fig.height=6} -p[[2]] <- p[[2]] + scale_fill_manual( - values = c("blue", "pink", "yellow"), - name = "bootstrap percentile", labels=c("lower", "non-significant", "higher"), - guide = guide_legend(reverse=TRUE) - ) -p +plot(hr_effect, show_effect_size = TRUE) +``` + +### Computing Statistics Directly + +You can access the significance and effect size calculations directly: + +```{r} +# Compute significance +compute_significance(hr_effect) + +# Compute effect sizes +compute_effect_size(hr_effect) +``` + +## Summary Tables + +Generate publication-ready summary tables with `besthr_table()`: + +```{r} +# Default tibble format +besthr_table(hr_effect) + +# With significance stars +besthr_table(hr_effect, include_significance = TRUE) + +# Markdown format +besthr_table(hr_est_1, format = "markdown") +``` + +Other formats available: `"html"`, `"latex"` + +## Publication Export + +Save plots directly to publication-quality files: + +```{r, eval=FALSE} +# Save to PNG (default 300 DPI) +save_besthr(hr_est_1, "figure1.png") + +# Save to PDF +save_besthr(hr_est_1, "figure1.pdf", width = 10, height = 8) + +# Save raincloud plot +save_besthr(hr_est_1, "raincloud.png", type = "raincloud") + +# With custom options +save_besthr(hr_est_1, "figure1.png", + theme = "modern", + colors = "okabe_ito", + width = 10, + height = 6, + dpi = 600) ``` +Supported formats: PNG, PDF, SVG, TIFF, JPEG