Showing preview only (2,370K chars total). Download the full file or copy to clipboard to get everything.
Repository: hughjonesd/santoku
Branch: master
Commit: 8933bb90a885
Files: 287
Total size: 2.2 MB
Directory structure:
gitextract_5kkl1r2r/
├── .Rbuildignore
├── .github/
│ ├── .gitignore
│ └── workflows/
│ ├── R-CMD-check.yaml
│ └── test-coverage.yaml
├── .gitignore
├── AGENTS.md
├── DESCRIPTION
├── LICENSE
├── LICENSE.md
├── NAMESPACE
├── NEWS.md
├── R/
│ ├── RcppExports.R
│ ├── breaks-by-group-size.R
│ ├── breaks-by-width.R
│ ├── breaks-impl.R
│ ├── breaks-misc.R
│ ├── breaks.R
│ ├── categorize.R
│ ├── chop-by-group-size.R
│ ├── chop-by-width.R
│ ├── chop-isolates.R
│ ├── chop-misc.R
│ ├── chop.R
│ ├── labels-datetime.R
│ ├── labels-glue.R
│ ├── labels-impl.R
│ ├── labels-single.R
│ ├── labels.R
│ ├── non-standard-types-doc.R
│ ├── santoku-cast.R
│ ├── santoku-package.R
│ ├── tab.R
│ └── utils.R
├── README.Rmd
├── README.md
├── TODO.md
├── _pkgdown.yml
├── advantages.Rmd
├── codecov.yml
├── cran-comments.md
├── docs/
│ ├── 404.html
│ ├── 404.md
│ ├── AGENTS.html
│ ├── AGENTS.md
│ ├── CLAUDE.html
│ ├── CLAUDE.md
│ ├── LICENSE-text.html
│ ├── LICENSE-text.md
│ ├── LICENSE.html
│ ├── LICENSE.md
│ ├── TODO.html
│ ├── TODO.md
│ ├── articles/
│ │ ├── index.html
│ │ ├── index.md
│ │ ├── santoku.html
│ │ ├── santoku.md
│ │ ├── santoku_files/
│ │ │ ├── accessible-code-block-0.0.1/
│ │ │ │ └── empty-anchor.js
│ │ │ ├── header-attrs-2.11/
│ │ │ │ └── header-attrs.js
│ │ │ └── header-attrs-2.8/
│ │ │ └── header-attrs.js
│ │ ├── website-articles/
│ │ │ ├── performance.html
│ │ │ ├── performance.md
│ │ │ └── performance_files/
│ │ │ ├── accessible-code-block-0.0.1/
│ │ │ │ └── empty-anchor.js
│ │ │ ├── header-attrs-2.11/
│ │ │ │ └── header-attrs.js
│ │ │ └── header-attrs-2.8/
│ │ │ └── header-attrs.js
│ │ ├── whats-new-in-0-9-0.html
│ │ └── whats-new-in-0-9-0.md
│ ├── authors.html
│ ├── authors.md
│ ├── bootstrap-toc.css
│ ├── bootstrap-toc.js
│ ├── deps/
│ │ ├── _Courier Prime-0.4.0/
│ │ │ └── font.css
│ │ ├── bootstrap-5.1.0/
│ │ │ └── font.css
│ │ ├── bootstrap-5.1.3/
│ │ │ └── font.css
│ │ ├── bootstrap-5.2.2/
│ │ │ └── font.css
│ │ ├── bootstrap-5.3.1/
│ │ │ └── font.css
│ │ ├── data-deps.txt
│ │ ├── font-awesome-6.5.2/
│ │ │ └── css/
│ │ │ ├── all.css
│ │ │ └── v4-shims.css
│ │ └── jquery-3.6.0/
│ │ └── jquery-3.6.0.js
│ ├── docsearch.css
│ ├── docsearch.js
│ ├── extra.css
│ ├── index.html
│ ├── index.md
│ ├── katex-auto.js
│ ├── lightswitch.js
│ ├── llms.txt
│ ├── news/
│ │ ├── index.html
│ │ └── index.md
│ ├── pkgdown.css
│ ├── pkgdown.js
│ ├── pkgdown.yml
│ ├── reference/
│ │ ├── breaks-class.html
│ │ ├── breaks-class.md
│ │ ├── brk-left-right.html
│ │ ├── brk-left-right.md
│ │ ├── brk_default.html
│ │ ├── brk_default.md
│ │ ├── brk_equally.html
│ │ ├── brk_evenly.html
│ │ ├── brk_fn.html
│ │ ├── brk_manual.html
│ │ ├── brk_manual.md
│ │ ├── brk_mean_sd.html
│ │ ├── brk_n.html
│ │ ├── brk_pretty.html
│ │ ├── brk_proportions.html
│ │ ├── brk_quantiles.html
│ │ ├── brk_spikes.html
│ │ ├── brk_width-for-datetime.html
│ │ ├── brk_width-for-datetime.md
│ │ ├── brk_width.Duration.html
│ │ ├── brk_width.default.html
│ │ ├── brk_width.html
│ │ ├── chop.html
│ │ ├── chop.md
│ │ ├── chop_deciles.html
│ │ ├── chop_equally.html
│ │ ├── chop_equally.md
│ │ ├── chop_evenly.html
│ │ ├── chop_evenly.md
│ │ ├── chop_fn.html
│ │ ├── chop_fn.md
│ │ ├── chop_mean_sd.html
│ │ ├── chop_mean_sd.md
│ │ ├── chop_n.html
│ │ ├── chop_n.md
│ │ ├── chop_pretty.html
│ │ ├── chop_pretty.md
│ │ ├── chop_proportions.html
│ │ ├── chop_proportions.md
│ │ ├── chop_quantiles.html
│ │ ├── chop_quantiles.md
│ │ ├── chop_spikes.html
│ │ ├── chop_spikes.md
│ │ ├── chop_width.html
│ │ ├── chop_width.md
│ │ ├── dissect.html
│ │ ├── dissect.md
│ │ ├── exactly.html
│ │ ├── exactly.md
│ │ ├── fillet.html
│ │ ├── fillet.md
│ │ ├── format.breaks.html
│ │ ├── index.html
│ │ ├── index.md
│ │ ├── is.breaks.html
│ │ ├── kiru.html
│ │ ├── knife.html
│ │ ├── knife.md
│ │ ├── lbl_dash.html
│ │ ├── lbl_dash.md
│ │ ├── lbl_datetime.html
│ │ ├── lbl_datetime.md
│ │ ├── lbl_discrete.html
│ │ ├── lbl_discrete.md
│ │ ├── lbl_endpoint.html
│ │ ├── lbl_endpoints.html
│ │ ├── lbl_endpoints.md
│ │ ├── lbl_format.html
│ │ ├── lbl_format.md
│ │ ├── lbl_glue.html
│ │ ├── lbl_glue.md
│ │ ├── lbl_intervals.html
│ │ ├── lbl_intervals.md
│ │ ├── lbl_manual.html
│ │ ├── lbl_manual.md
│ │ ├── lbl_midpoints.html
│ │ ├── lbl_midpoints.md
│ │ ├── lbl_seq.html
│ │ ├── lbl_seq.md
│ │ ├── non-standard-types.html
│ │ ├── non-standard-types.md
│ │ ├── percent.html
│ │ ├── percent.md
│ │ ├── print.breaks.html
│ │ ├── santoku-cast.html
│ │ ├── santoku-cast.md
│ │ ├── santoku-package.html
│ │ ├── santoku-package.md
│ │ ├── santoku.html
│ │ ├── santoku_cast_common.Date.html
│ │ ├── santoku_cast_common.POSIXct.html
│ │ ├── santoku_cast_common.default.html
│ │ ├── santoku_cast_common.double.html
│ │ ├── santoku_cast_common.hexmode.html
│ │ ├── santoku_cast_common.integer64.html
│ │ ├── santoku_cast_common.octmode.html
│ │ ├── santoku_cast_common.ts.html
│ │ ├── santoku_cast_common.zoo.html
│ │ ├── sequence-labels.html
│ │ ├── tab.html
│ │ ├── tab_deciles.html
│ │ ├── tab_dissect.html
│ │ ├── tab_equally.html
│ │ ├── tab_evenly.html
│ │ ├── tab_fn.html
│ │ ├── tab_mean_sd.html
│ │ ├── tab_n.html
│ │ ├── tab_pretty.html
│ │ ├── tab_proportions.html
│ │ ├── tab_quantiles.html
│ │ ├── tab_spikes.html
│ │ └── tab_width.html
│ ├── search.json
│ ├── sitemap.xml
│ └── tutorials/
│ ├── 00-visualintroduction.html
│ ├── 00-visualintroduction.md
│ ├── 01-chopping-dates.html
│ ├── 01-chopping-dates.md
│ ├── index.html
│ └── index.md
├── man/
│ ├── breaks-class.Rd
│ ├── brk_default.Rd
│ ├── brk_manual.Rd
│ ├── brk_width-for-datetime.Rd
│ ├── chop.Rd
│ ├── chop_equally.Rd
│ ├── chop_evenly.Rd
│ ├── chop_fn.Rd
│ ├── chop_mean_sd.Rd
│ ├── chop_n.Rd
│ ├── chop_pretty.Rd
│ ├── chop_proportions.Rd
│ ├── chop_quantiles.Rd
│ ├── chop_spikes.Rd
│ ├── chop_width.Rd
│ ├── dissect.Rd
│ ├── exactly.Rd
│ ├── fillet.Rd
│ ├── lbl_dash.Rd
│ ├── lbl_datetime.Rd
│ ├── lbl_discrete.Rd
│ ├── lbl_endpoints.Rd
│ ├── lbl_glue.Rd
│ ├── lbl_intervals.Rd
│ ├── lbl_manual.Rd
│ ├── lbl_midpoints.Rd
│ ├── lbl_seq.Rd
│ ├── non-standard-types.Rd
│ ├── percent.Rd
│ ├── santoku-cast.Rd
│ └── santoku-package.Rd
├── pkgdown/
│ └── extra.css
├── release-process.R
├── santoku.Rproj
├── src/
│ ├── .gitignore
│ ├── RcppExports.cpp
│ └── categorize.cpp
├── tests/
│ ├── testthat/
│ │ ├── test-Date-DateTime.R
│ │ ├── test-breaks.R
│ │ ├── test-categorize.R
│ │ ├── test-chop.R
│ │ ├── test-labels.R
│ │ ├── test-nonstandard.R
│ │ ├── test-tab.R
│ │ └── test-zzz-systematic.R
│ └── testthat.R
└── vignettes/
├── .gitignore
├── santoku.Rmd
├── tutorials/
│ ├── chopping-dates-with-santoku.Rmd
│ ├── libs/
│ │ ├── Proj4Leaflet/
│ │ │ ├── proj4-compressed.js
│ │ │ └── proj4leaflet.js
│ │ ├── crosstalk/
│ │ │ ├── css/
│ │ │ │ └── crosstalk.css
│ │ │ └── js/
│ │ │ └── crosstalk.js
│ │ ├── datatables-binding/
│ │ │ └── datatables.js
│ │ ├── datatables-css/
│ │ │ └── datatables-crosstalk.css
│ │ ├── dt-core/
│ │ │ └── css/
│ │ │ └── jquery.dataTables.extra.css
│ │ ├── header-attrs/
│ │ │ └── header-attrs.js
│ │ ├── htmlwidgets/
│ │ │ └── htmlwidgets.js
│ │ ├── leaflet/
│ │ │ ├── leaflet.css
│ │ │ └── leaflet.js
│ │ ├── leaflet-binding/
│ │ │ └── leaflet.js
│ │ ├── leafletfix/
│ │ │ └── leafletfix.css
│ │ ├── remark-css/
│ │ │ ├── default-fonts.css
│ │ │ └── default.css
│ │ └── rstudio_leaflet/
│ │ └── rstudio_leaflet.css
│ ├── rsconnect/
│ │ └── documents/
│ │ └── visual-introduction.Rmd/
│ │ └── rpubs.com/
│ │ └── rpubs/
│ │ ├── Document.dcf
│ │ └── Publish Document.dcf
│ ├── visual-intro-styles.css
│ └── visual-introduction.Rmd
├── website-articles/
│ ├── performance.Rmd
│ └── performance_cache/
│ └── html/
│ ├── __packages
│ ├── unnamed-chunk-1_55ef34d700344288a08acda554dff8ac.RData
│ ├── unnamed-chunk-1_55ef34d700344288a08acda554dff8ac.rdb
│ └── unnamed-chunk-1_55ef34d700344288a08acda554dff8ac.rdx
└── whats-new-in-0-9-0.Rmd
================================================
FILE CONTENTS
================================================
================================================
FILE: .Rbuildignore
================================================
^.*\.Rproj$
^\.Rproj\.user$
^LICENSE\.md$
^TODO\.md$
^README\.Rmd$
^AGENTS\.md$
^index\.Rmd$
^advantages\.Rmd$
^release-process\.R$
^\.travis\.yml$
^appveyor\.yml$
^_pkgdown\.yml$
^docs$
^pkgdown$
^codecov\.yml$
website-articles
tutorials
revdep
^doc$
^Meta$
^cran-comments\.md$
^CRAN-RELEASE$
^\.github$
^CRAN-SUBMISSION$
^\.positai$
^\.claude$
================================================
FILE: .github/.gitignore
================================================
*.html
================================================
FILE: .github/workflows/R-CMD-check.yaml
================================================
# 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]
pull_request:
name: R-CMD-check.yaml
permissions: read-all
jobs:
R-CMD-check:
runs-on: ${{ matrix.config.os }}
name: ${{ matrix.config.os }} (${{ matrix.config.r }})
strategy:
fail-fast: false
matrix:
config:
- {os: macos-latest, r: 'release'}
- {os: windows-latest, r: 'release'}
- {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'}
- {os: ubuntu-latest, r: 'release'}
- {os: ubuntu-latest, r: 'oldrel-1'}
env:
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
R_KEEP_PKG_SOURCE: yes
steps:
- 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: 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")'
================================================
FILE: .github/workflows/test-coverage.yaml
================================================
# 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]
pull_request:
name: test-coverage.yaml
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")
)
print(cov)
covr::to_cobertura(cov)
shell: Rscript {0}
- uses: codecov/codecov-action@v5
with:
# Fail if error if not on PR, or if on PR and token is given
fail_ci_if_error: ${{ github.event_name != 'pull_request' || secrets.CODECOV_TOKEN }}
files: ./cobertura.xml
plugins: 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
================================================
FILE: .gitignore
================================================
.Rproj.user
.Rhistory
.RData
.Ruserdata
inst/doc
doc
Meta
.DS_Store
revdep/**
.positai
================================================
FILE: AGENTS.md
================================================
# AGENTS.md
This file provides guidance to agents when working with code in this repository.
## Project Overview
santoku is an R package that provides `chop()`, a versatile replacement for
`base::cut()` for cutting data into intervals. The package handles numeric
vectors, dates, times, and other comparable objects, with support for singleton
intervals and flexible labeling.
## Common Commands
### Testing
```r
# Run all tests
devtools::test()
# Run tests from command line
R CMD check .
# Run specific test file
testthat::test_file("tests/testthat/test-chop.R")
```
### Development workflow
```r
# Build package
devtools::build()
# Install package locally
devtools::install()
# Check package
devtools::check()
# Load package for interactive development
devtools::load_all()
```
### Documentation
```r
# Update documentation
devtools::document()
# Build website
pkgdown::build_site()
```
## Architecture
### Core Components
- **Main cutting function**: `chop()` in `R/chop.R` - the primary interface that calls other functions
- **Break creation**: `R/breaks*.R` files contain functions to create break points (`brk_*` functions)
- **Labeling system**: `R/labels*.R` files contain labeling functions (`lbl_*` functions)
- **Convenience functions**: `R/chop-*.R` files contain `chop_*` wrapper functions for common use cases
- **C++ backend**: `src/categorize.cpp` provides fast interval categorization via Rcpp
- **Tabulation**: `R/tab.R` provides `tab_*` functions that chop and tabulate in one step
### Key Design Patterns
1. **Function factories**: Many functions return other functions (e.g., `brk_*` functions return break-creation functions)
2. **Method dispatch**: Uses S3 methods and vctrs for handling different data types (numbers, dates, etc.)
3. **Extensible labeling**: Label functions can be combined and customized using the `lbl_*` family
4. **Performance**: Core categorization logic is implemented in C++ for speed
### File Organization
- `R/chop.R` - Main `chop()` function and documentation
- `R/breaks*.R` - Break point creation (`brk_default`, `brk_width`, etc.)
- `R/labels*.R` - Label generation (`lbl_intervals`, `lbl_dash`, etc.)
- `R/chop-*.R` - Convenience functions (`chop_quantiles`, `chop_width`, etc.)
- `R/tab.R` - Tabulation functions
- `R/utils.R` - Utility functions like `exactly()` and `percent()`
- `src/categorize.cpp` - Fast C++ categorization implementation
- `tests/testthat/` - Comprehensive test suite
## Development Notes
- The package uses Rcpp for performance-critical categorization
- Tests are extensive and include systematic testing in `test-zzz-systematic.R`
- The package supports non-standard data types (dates, times, units) via the vctrs package
- Documentation follows roxygen2 conventions with extensive examples
- Uses lifecycle package for function lifecycle management
================================================
FILE: DESCRIPTION
================================================
Package: santoku
Type: Package
Title: A Versatile Cutting Tool
Version: 1.2.1
Authors@R:
c(
person(given = "David",
family = "Hugh-Jones",
role = c("aut", "cre"),
email = "davidhughjones@gmail.com"),
person(given = "Daniel",
family = "Possenriede",
role = c("ctb"),
email = "possenriede@gmail.com")
)
Maintainer: David Hugh-Jones <davidhughjones@gmail.com>
Description: A tool for cutting data into intervals. Allows singleton intervals.
Always includes the whole range of data by default. Flexible labelling.
Convenience functions for cutting by quantiles etc. Handles dates, times, units
and other vectors.
License: MIT + file LICENSE
Encoding: UTF-8
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.3.3
Suggests:
bench,
bit64,
covr,
haven,
Hmisc,
hms,
knitr,
lubridate,
purrr,
rmarkdown,
scales,
stringi,
testthat (>= 3.2.0),
units,
withr,
xts,
zoo
Config/testthat/edition: 3
LinkingTo:
Rcpp
Depends:
R (>= 3.5.0)
Imports:
Rcpp,
assertthat,
glue,
lifecycle,
rlang,
vctrs
URL: https://github.com/hughjonesd/santoku, https://hughjonesd.github.io/santoku/
BugReports: https://github.com/hughjonesd/santoku/issues
VignetteBuilder: knitr
RdMacros: lifecycle
================================================
FILE: LICENSE
================================================
YEAR: 2020
COPYRIGHT HOLDER: David Hugh-Jones
================================================
FILE: LICENSE.md
================================================
# MIT License
Copyright (c) 2019 David Hugh-Jones
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: NAMESPACE
================================================
# Generated by roxygen2: do not edit by hand
S3method(apply_format,"function")
S3method(apply_format,character)
S3method(apply_format,list)
S3method(apply_format.character,character)
S3method(apply_format.character,default)
S3method(apply_format.character,numeric)
S3method(apply_format.list,default)
S3method(as.double,breaks)
S3method(brk_width,Duration)
S3method(brk_width,default)
S3method(class_bounds,Date)
S3method(class_bounds,POSIXct)
S3method(class_bounds,default)
S3method(class_bounds,difftime)
S3method(class_bounds,integer64)
S3method(class_bounds,numeric)
S3method(class_bounds,units)
S3method(class_bounds,zoo)
S3method(endpoint_labels,Date)
S3method(endpoint_labels,POSIXt)
S3method(endpoint_labels,default)
S3method(endpoint_labels,double)
S3method(endpoint_labels,integer)
S3method(endpoint_labels,numeric)
S3method(endpoint_labels,quantileBreaks)
S3method(endpoint_labels,sdBreaks)
S3method(format,breaks)
S3method(print,breaks)
S3method(santoku_cast_common,Date)
S3method(santoku_cast_common,Date.Date)
S3method(santoku_cast_common,Date.POSIXct)
S3method(santoku_cast_common,POSIXct)
S3method(santoku_cast_common,POSIXct.Date)
S3method(santoku_cast_common,POSIXct.POSIXct)
S3method(santoku_cast_common,default)
S3method(santoku_cast_common,default.default)
S3method(santoku_cast_common,default.hexmode)
S3method(santoku_cast_common,default.integer64)
S3method(santoku_cast_common,default.octmode)
S3method(santoku_cast_common,default.ts)
S3method(santoku_cast_common,default.zoo)
S3method(santoku_cast_common,double)
S3method(santoku_cast_common,double.default)
S3method(santoku_cast_common,double.integer64)
S3method(santoku_cast_common,hexmode)
S3method(santoku_cast_common,hexmode.default)
S3method(santoku_cast_common,hexmode.hexmode)
S3method(santoku_cast_common,integer64)
S3method(santoku_cast_common,integer64.default)
S3method(santoku_cast_common,integer64.double)
S3method(santoku_cast_common,integer64.integer64)
S3method(santoku_cast_common,octmode)
S3method(santoku_cast_common,octmode.default)
S3method(santoku_cast_common,octmode.octmode)
S3method(santoku_cast_common,ts)
S3method(santoku_cast_common,ts.default)
S3method(santoku_cast_common,zoo)
S3method(santoku_cast_common,zoo.default)
S3method(scaled_endpoints,breaks)
S3method(scaled_endpoints,default)
S3method(sequence_width,Period)
S3method(sequence_width,default)
export(brk_default)
export(brk_equally)
export(brk_evenly)
export(brk_fn)
export(brk_manual)
export(brk_mean_sd)
export(brk_n)
export(brk_pretty)
export(brk_proportions)
export(brk_quantiles)
export(brk_spikes)
export(brk_width)
export(chop)
export(chop_deciles)
export(chop_equally)
export(chop_evenly)
export(chop_fn)
export(chop_mean_sd)
export(chop_n)
export(chop_pretty)
export(chop_proportions)
export(chop_quantiles)
export(chop_spikes)
export(chop_width)
export(dissect)
export(exactly)
export(fillet)
export(is.breaks)
export(kiru)
export(lbl_dash)
export(lbl_date)
export(lbl_datetime)
export(lbl_discrete)
export(lbl_endpoint)
export(lbl_endpoints)
export(lbl_glue)
export(lbl_intervals)
export(lbl_manual)
export(lbl_midpoints)
export(lbl_seq)
export(percent)
export(santoku_cast_common.Date)
export(santoku_cast_common.POSIXct)
export(santoku_cast_common.default)
export(santoku_cast_common.double)
export(santoku_cast_common.hexmode)
export(santoku_cast_common.integer64)
export(santoku_cast_common.octmode)
export(santoku_cast_common.ts)
export(santoku_cast_common.zoo)
export(tab)
export(tab_deciles)
export(tab_dissect)
export(tab_equally)
export(tab_evenly)
export(tab_fn)
export(tab_mean_sd)
export(tab_n)
export(tab_pretty)
export(tab_proportions)
export(tab_quantiles)
export(tab_spikes)
export(tab_width)
import(assertthat)
importFrom(Rcpp,sourceCpp)
importFrom(lifecycle,deprecated)
useDynLib(santoku, .registration = TRUE)
================================================
FILE: NEWS.md
================================================
# santoku 1.2.1
* Fixed a test bug.
# santoku 1.2.0
* New experimental `lbl_date()` and `lbl_datetime()` functions for pretty
formatting of dates and date-times.
* Bugfix: extended breaks were failing on `haven::labelled` objects.
* The `raw` argument to `lbl_*` functions, deprecated since 0.9.0, now throws
an error.
# santoku 1.1.0
* Core logic has been speeded up using raw pointers. This was vibe-coded by me
and Claude Code. If it breaks, please file a bug report.
* The experimental `chop_spikes()` and `dissect()` functions give
common values of `x` their own singleton intervals.
* On Unicode platforms, infinity will be represented as ∞ in breaks. Set
`options(santoku.infinity = "Inf")` to use the old behaviour.
* Singleton breaks are not labelled specially by default in
`chop_quantiles(..., raw = FALSE)`. This means that e.g. if the 10th and 20th
percentiles are both the same number, the label will still be `[10%, 20%]`.
* When multiple quantiles are the same, santoku warns and returns the leftmost
quantile interval. Before it would merge the intervals, creating labels that
might be different to what the user asked for.
* `chop_quantiles()` gains a `recalc_probs` argument. `recalc_probs = TRUE`
recalculates probabilities using `ecdf(x)`, which may give more accurate
interval labels.
* `single = NULL` has been documented explicitly in `lbl_*` functions.
* Bugfix: `brk_manual()` no longer warns if `close_end = TRUE` (the default).
# santoku 1.0.0
* santoku is now considered stable.
* `chop_quantiles()` and `brk_quantiles()` gain a new `weights` argument,
letting you chop by weighted quantiles using `Hmisc::wtd.quantile()`.
* `brk_quantiles()` may now return singleton breaks, producing more
accurate results when `x` has duplicate elements.
* Some deprecated functions have been removed, and the `raw` argument to
`lbl_*` functions now always gives a deprecation warning.
# santoku 0.10.0
* List arguments to `fmt` in `lbl_*` functions will be taken as arguments to
`base::format`. This gives more flexibility in formatting, e.g., `units`
breaks.
* `chop_n()` gains a `tail` argument, to deal with a last interval containing
less than `n` elements. Set `tail = "merge"` to merge it with
the previous interval. This guarantees that all intervals contain at least
`n` elements.
* `chop_equally()` may return fewer than `groups` groups when there are
duplicate elements. We now warn when this happens.
* Bugfix: `chop_n()` could return intervals with fewer than `n` elements
when there were duplicate elements. The new algorithm avoids this, but
may be slower in this case.
# santoku 0.9.1
* `endpoint_labels()` methods gain an unused `...` argument to satisfy R CMD CHECK.
# santoku 0.9.0
## Breaking changes
There are important changes to `close_end`.
* `close_end` is now `TRUE` by default in `chop()` and `fillet()`.
In previous versions:
```r
chop(1:2, 1:2)
## [1] [1, 2) {2}
## Levels: [1, 2) {2}
```
Whereas now:
```r
chop(1:2, 1:2)
## [1] [1, 2] [1, 2]
## Levels: [1, 2]
```
* `close_end` is now always applied after `extend`. For example, in previous
versions:
```r
chop(1:4, 2:3, close_end = TRUE)
## [1] [1, 2) [2, 3] [2, 3] (3, 4]
## Levels: [1, 2) [2, 3] (3, 4]
```
Whereas now:
```r
chop(1:4, 2:3, close_end = TRUE)
## [1] [1, 2) [2, 3) [3, 4] [3, 4]
## Levels: [1, 2) [2, 3) [3, 4]
```
We changed this behaviour to be more in line with user expectations.
* If `breaks` has names, they will be used as labels:
```r
chop(1:5, c(Low = 1, Mid = 2, High = 4))
## [1] Low Mid Mid High High
## Levels: Low Mid High
```
Names can also be used for labels in `probs` in `chop_quantiles()` and
`proportions` in `chop_proportions()`.
* There is a new `raw` parameter to `chop()`. This replaces the parameter
`raw` in `lbl_*` functions, which is now soft-deprecated.
* `lbl_manual()` is deprecated. Just use a vector argument to `labels` instead.
* A `labels` argument to `chop_quantiles()` now needs to be explicitly named.
I expect these to be the last important breaking changes before we release
version 1.0 and mark the package as "stable". If they cause problems for you,
please file an issue.
## Other changes
* New `chop_fn()`, `brk_fn()` and `tab_fn()` chop using an arbitrary function.
* Added section on non-standard objects to vignette.
# santoku 0.8.0
## Breaking changes
* `lbl_endpoint()` has been renamed to `lbl_endpoints()`. The old version will
trigger a deprecation warning. `lbl_endpoints()` gains `first`, `last` and
`single` arguments like other labelling functions.
## Other changes
* New `chop_pretty()`, `brk_pretty()` and `tab_pretty()` functions use
`base::pretty()` to calculate attractive breakpoints. Thanks @davidhodge931.
* New `chop_proportions()`, `brk_proportions()` and `tab_proportions()`
functions chop `x` into proportions of its range.
* `chop_equally()` now uses `lbl_intervals(raw = TRUE)` by default, bringing it
into line with `chop_evenly()`, `chop_width()` and `chop_n()`.
* New `lbl_midpoints()` function labels breaks by their midpoints.
* `lbl_discrete()` gains a `single` argument.
* You can now chop `ts`, `xts::xts` and `zoo::zoo` objects.
* `chop()` is more forgiving when mixing different types, e.g.:
- `Date` objects with `POSIXct` breaks, and vice versa
- `bit64::integer64` and `double`s
* Bugfix: `lbl_discrete()` sometimes had ugly label formatting.
# santoku 0.7.0
## Breaking changes
* In labelling functions, `first` and `last` arguments are now passed to
`glue::glue()`. Variables `l` and `r` represent the left and right endpoints
of the intervals.
* `chop_mean_sd()` now takes a vector `sds` of standard deviations, rather than
a single maximum number `sd` of standard deviations. Write e.g.
`chop_mean_sd(sds = 1:3)` rather than `chop_mean_sd(sd = 3)`. The `sd` argument
is deprecated.
* The `groups` argument to `chop_evenly()`, deprecated in 0.4.0, has
been removed.
* `brk_left()` and `brk_right()`, deprecated in 0.4.0, have been removed.
* `knife()`, deprecated in 0.4.0, has been removed.
* `lbl_format()`, questioning since 0.4.0, has been removed.
* Arguments of `lbl_dash()` and `lbl_intervals()` have been reordered for
consistency with other labelling functions.
## Other changes
* You can now chop many more types, including `units` from the `units` package,
`difftime` objects, `package_version` objects, etc.
- Character vectors will be chopped by lexicographic order, with an optional warning.
- If you have problems chopping a vector type, file a bug report.
* The `{glue}` package has become a hard dependency. It is used in many places to
format labels.
* There is a new `lbl_glue()` function using the `{glue}` package. Thanks to @dpprdan.
* You can now set `labels = NULL` to return integer codes.
* Arguments `first`, `last` and `single` can be used in `lbl_intervals()`
and `lbl_dash()`, to override the first and last interval labels, or to
label singleton intervals.
* `lbl_dash()` and `lbl_discrete()` use unicode em-dash where possible.
* `brk_default()` throws an error if breaks are not sorted.
## Bugfixes
* Bugfix: `tab()` and friends no longer display an `x` as the variable name.
* Bugfix: `lbl_endpoint()` was erroring for some types of breaks.
# santoku 0.6.0
* New arguments `first` and `last` in `lbl_dash()` and `lbl_discrete()` allow you
to override the first and last interval labels.
* Fixes for CRAN.
# santoku 0.5.0
* Negative numbers can be used in `chop_width()`.
- This sets `left = FALSE` by default.
- Also works for negative time intervals.
# santoku 0.4.1
* Bugfix: `chop(1:4, 1)` was erroring.
# santoku 0.4.0
## Interface changes
The new version has some interface changes. These are based on user experience,
and are designed to make using `chop()` more intuitive and predictable.
* `chop()` has two new arguments, `left` and `close_end`.
- Using `left = FALSE` is simpler and more intuitive than wrapping
breaks in `brk_right()`.
- `brk_left()` and `brk_right()` have been kept for now, but cannot be used to
wrap other break functions.
- Using `close_end` is simpler than passing `close_end` into
`brk_left()` or `brk_right()` (which no longer accept this argument directly).
- `left = TRUE` by default, except for non-numeric objects in
`chop_quantiles()` and `chop_equally()`, where `left = FALSE` works better.
* `close_end` is now `FALSE` by default.
- This prevents user surprises when e.g. `chop(3, 1:3)` puts `3` into a
different category than `chop(3, 1:4)`.
- `close_end` is `TRUE` by default for `chop_quantiles()`, `chop_n()` and
similar functions. This ensures that e.g.
`chop_quantiles(x, c(0, 1/3, 2/3, 1))` does what you would expect.
* The `groups` argument to `chop_evenly()` has been renamed from `groups` to
`intervals`. This should make it easier to remember the difference between
`chop_evenly()` and `chop_equally()`. (Chop evenly into `n` equal-width
*intervals*, or chop equally into `n` equal-sized *groups*.)
* `knife()` has been deprecated to keep the interface slim and
focused. Use `purrr::partial()` instead.
## Other changes
* Date and datetime (`POSIXct`) objects can now be chopped.
- `chop_width()` accepts `difftime`, `lubridate::period` or `lubridate::duration`
objects
- all other `chop_` functions work as well.
* Many labelling functions have a new `fmt` argument. This can be a string
interpreted by `sprintf()` or `format()`, or a 1-argument formatting function
for break endpoints, e.g. `scales::label_percent()`.
* Experimental: `lbl_discrete()` for discrete data such as integers or (most)
dates.
* There is a new `lbl_endpoint()` function for labelling intervals solely by
their left or right endpoint.
* `brk_mean_sd()` now accepts non-integer positive numbers.
* Add `brk_equally()` for symmetry with `chop_equally()`.
* Minor tweaks to `chop_deciles()`.
* Bugfix: `lbl_format()` wasn't accepting numeric formats, even when
`raw = TRUE`. Thanks to Sharla Gelfand.
# santoku 0.3.0
* First CRAN release.
* Changed `kut()` to `kiru()`. `kiru()` is an alternative spelling for `chop()`,
for use when the tidyr package is loaded.
* `lbl_sequence()` has become `lbl_manual()`.
* `lbl_letters()` and friends have been replaced by `lbl_seq()`:
- to replace `lbl_letters()` use `lbl_seq()`
- to replace `lbl_LETTERS()` use `lbl_seq("A")`
- to replace `lbl_roman()` use `lbl_seq("i")`
- to replace `lbl_ROMAN()` use `lbl_seq("I")`
- to replace `lbl_numerals()` use `lbl_seq("1")`
- for more complex formatting use e.g. `lbl_seq("A:")`, `lbl_seq("(i)")`
# santoku 0.2.0
* Added a `NEWS.md` file to track changes to the package.
* Default labels when `extend = NULL` have changed, from
`[-Inf, ...` and `..., Inf]` to `[min(x), ...` and `..., max(x)]`.
================================================
FILE: R/RcppExports.R
================================================
# Generated by using Rcpp::compileAttributes() -> do not edit by hand
# Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393
categorize_impl <- function(x, breaks, left) {
.Call(`_santoku_categorize_impl`, x, breaks, left)
}
================================================
FILE: R/breaks-by-group-size.R
================================================
#' @rdname chop_quantiles
#'
#' @export
#' @order 2
brk_quantiles <- function (probs, ..., weights = NULL, recalc_probs = FALSE) {
assert_that(
is.numeric(probs),
noNA(probs),
all(probs >= 0),
all(probs <= 1),
is.null(weights) || is.numeric(weights),
is.flag(recalc_probs)
)
probs <- sort(probs)
function (x, extend, left, close_end) {
dots <- list(...)
dots$x <- x
if (! is.numeric(x) && ! "type" %in% names(dots)) dots$type <- 1
dots$probs <- probs
dots$na.rm <- TRUE
qs <- if (is.null(weights)) {
do.call(stats::quantile, dots)
} else {
rlang::check_installed("Hmisc",
reason = "to use `weights` in brk_quantiles()")
dots$weights <- weights
do.call(Hmisc::wtd.quantile, dots)
}
if (anyNA(qs)) return(empty_breaks()) # data was all NA
if (anyDuplicated(qs) > 0L) {
if (! recalc_probs) {
warning("`x` has duplicate quantiles: break labels may be misleading")
}
# We use the left-most probabilities, so e.g. if 0%, 20% and 40% quantiles
# are all the same number, we'll use the category [0%, 20%).
# This means we always return intervals that the user asked for, though
# they may be more misleading than e.g. [0%, 40%).
illegal_dupes <- find_illegal_duplicates(qs)
qs <- qs[! illegal_dupes]
probs <- probs[! illegal_dupes]
}
breaks <- create_lr_breaks(qs, left)
needs <- needs_extend(breaks, x, extend, left, close_end)
if ((needs & LEFT) > 0) probs <- c(0, probs)
if ((needs & RIGHT) > 0) probs <- c(probs, 1)
breaks <- extend_and_close(breaks, x, extend, left, close_end)
class(breaks) <- c("quantileBreaks", class(breaks))
if (recalc_probs) {
probs <- calculate_ecdf_probs(x, breaks, weights)
}
attr(breaks, "scaled_endpoints") <- probs
names(breaks) <- names(probs)
breaks
}
}
#' Calculate the proportions of `x` that is strictly/weakly less than
#' each break
#'
#' @param x A numeric vector
#' @param breaks A breaks object
#' @param weights A vector of weights. Non-NULL weights are unimplemented
#'
#' @return A vector of proportions of `x` that are strictly less than
#' left-closed breaks, and weakly less than right-closed breaks.
#'
#' @noRd
calculate_ecdf_probs <- function (x, breaks, weights) {
if (! is.numeric(x)) {
stop("`recalc_probs = TRUE` can only be used with numeric `x`")
}
if (! is.null(weights)) {
stop("`recalc_probs = TRUE` cannot be used with non-null `weights`")
}
brk_vec <- unclass_breaks(breaks)
left_vec <- attr(breaks, "left")
# proportion of x that is weakly less than x
prop_lte_brk <- stats::ecdf(x)(brk_vec)
# proportion of x that is strictly less than x
prop_lt_brk <- 1 - stats::ecdf(-x)(-brk_vec)
probs <- ifelse(left_vec, prop_lt_brk, prop_lte_brk)
# Suppose your breaks are [a, b].
# You want to expand this?
probs
}
#' @rdname chop_equally
#'
#' @export
#' @order 2
brk_equally <- function (groups) {
assert_that(is.count(groups))
brq <- brk_quantiles(seq(0L, groups)/groups)
function (x, extend, left, close_end) {
breaks <- brq(x = x, extend = extend, left = left, close_end = close_end)
if (length(breaks) < groups + 1) {
warning("Fewer than ", groups, " intervals created")
}
breaks
}
}
#' @rdname chop_n
#' @export
#' @order 2
brk_n <- function (n, tail = "split") {
assert_that(is.count(n), tail == "split" || tail == "merge")
function (x, extend, left, close_end) {
xs <- sort(x, decreasing = ! left, na.last = NA) # remove NAs
if (length(xs) < 1L) return(empty_breaks())
dupes <- duplicated(xs)
breaks <- xs[0] # ensures breaks has type of xs
last_x <- xs[length(xs)]
maybe_merge_tail <- function (breaks, tail) {
if (tail == "merge" && length(breaks) > 1) {
breaks <- breaks[-length(breaks)]
}
breaks
}
# Idea of the algorithm:
# Loop:
# if there are no dupes, just take a sequence of each nth element
# starting at 1, and exit
# if there are remaining dupes, then take the first element
# set m to the (n+1)th element which would normally be next
# if element m is a dupe:
# - we need to go up, otherwise elements to the left will be in the next
# interval, and this interval will be too small
# - so set m to the next non-dupe (i.e. strictly larger) element
# now delete the first m-1 elements
# And repeat
while (TRUE) {
if (! any(dupes)) {
breaks <- c(breaks, xs[seq(1L, length(xs), n)])
if (length(xs) %% n > 0) {
breaks <- maybe_merge_tail(breaks, tail)
}
break
}
breaks <- c(breaks, xs[1])
m <- n + 1
if (length(xs) <= n || all(dupes[-seq_len(m - 1)])) {
if (length(xs) < n) {
breaks <- maybe_merge_tail(breaks, tail)
}
break
}
if (dupes[m]) {
# the first non-dupe will be the next element that is different
# we know there is one, because we checked above
m <- m + match(FALSE, dupes[-(1:m)])
}
discard <- seq_len(m - 1)
xs <- xs[-discard]
dupes <- dupes[-discard]
}
breaks <- c(breaks, last_x)
if (! left) breaks <- rev(breaks)
breaks <- create_extended_breaks(breaks, x, extend, left, close_end)
breaks
}
}
================================================
FILE: R/breaks-by-width.R
================================================
#' Equal-width intervals for dates or datetimes
#'
#' `brk_width()` can be used with time interval classes from base R or the
#' `lubridate` package.
#'
#' @param width A scalar [difftime], [Period][lubridate::Period-class] or
#' [Duration][lubridate::Duration-class] object.
#'
#' @param start A scalar of class [Date][base::Dates] or [POSIXct][DateTimeClasses].
#' Can be omitted.
#'
#' @details
#' If `width` is a Period, [`lubridate::add_with_rollback()`][`lubridate::m+`]
#' is used to calculate the widths. This can be useful for e.g. calendar months.
#'
#' @examples
#'
#' if (requireNamespace("lubridate")) {
#' year2001 <- as.Date("2001-01-01") + 0:364
#' tab_width(year2001, months(1),
#' labels = lbl_discrete(" to ", fmt = "%e %b %y"))
#' }
#'
#' @name brk_width-for-datetime
NULL
#' @rdname chop_width
#' @export
#' @order 2
brk_width <- function (width, start) UseMethod("brk_width")
#' @rdname brk_width-for-datetime
#' @export
brk_width.Duration <- function (width, start) {
loadNamespace("lubridate")
width <- lubridate::make_difftime(as.numeric(width))
NextMethod()
}
#' @rdname chop_width
#' @export
#' @order 2
brk_width.default <- function (width, start) {
assert_that(is.scalar(width))
sm <- missing(start)
if (! sm) assert_that(is.scalar(start))
function (x, extend, left, close_end) {
# finite if x has any non-NA finite elements:
min_x <- quiet_min(x[is.finite(x)])
max_x <- quiet_max(x[is.finite(x)])
if (sm) {
start <- if (sign(width) > 0) min_x else max_x
}
until <- if (sign(width) > 0) max_x else min_x
if (is.finite(start) && is.finite(until)) {
breaks <- sequence_width(width, start, until)
} else {
return(empty_breaks())
}
if (sign(width) <= 0) breaks <- rev(breaks)
breaks <- create_extended_breaks(breaks, x, extend, left, close_end)
breaks
}
}
#' @rdname chop_evenly
#' @export
#' @order 2
brk_evenly <- function(intervals) {
assert_that(is.count(intervals))
function (x, extend, left, close_end) {
min_x <- quiet_min(x[is.finite(x)])
max_x <- quiet_max(x[is.finite(x)])
if (sign(max_x - min_x) <= 0) return(empty_breaks())
breaks <- seq(min_x, max_x, length.out = intervals + 1L)
breaks <- create_extended_breaks(breaks, x, extend, left, close_end)
breaks
}
}
#' @rdname chop_proportions
#' @export
#' @order 2
brk_proportions <- function(proportions) {
assert_that(is.numeric(proportions), noNA(proportions),
all(proportions >= 0), all(proportions <= 1))
proportions <- sort(proportions)
function (x, extend, left, close_end) {
min_x <- quiet_min(x[is.finite(x)])
max_x <- quiet_max(x[is.finite(x)])
range_x <- max_x - min_x
if (sign(range_x) <= 0) return(empty_breaks())
breaks <- min_x + range_x * proportions
breaks <- create_lr_breaks(breaks, left)
scaled_endpoints <- proportions
needs <- needs_extend(breaks, x, extend, left, close_end)
if ((needs & LEFT) > 0) scaled_endpoints <- c(0, scaled_endpoints)
if ((needs & RIGHT) > 0) scaled_endpoints <- c(scaled_endpoints, 1)
breaks <- extend_and_close(breaks, x, extend, left, close_end)
attr(breaks, "scaled_endpoints") <- scaled_endpoints
names(breaks) <- names(scaled_endpoints)
breaks
}
}
================================================
FILE: R/breaks-impl.R
================================================
#' Create a breaks object
#'
#' @param obj A sorted vector or a `breaks` object.
#' @param left A logical vector, same length as `obj`.
#'
#' @return A breaks object
#'
#' @noRd
#'
create_breaks <- function (obj, left) {
if (anyNA(obj)) stop("breaks contained NAs")
stopifnot(all(obj == sort(obj)))
stopifnot(is.logical(left))
stopifnot(length(left) == length(obj))
if (any(find_illegal_duplicates(obj))) {
stop("breaks contained more than two consecutive equal values")
}
singletons <- singletons(obj)
l_singletons <- c(singletons, FALSE)
r_singletons <- c(FALSE, singletons)
stopifnot(all(left[l_singletons]))
stopifnot(all(! left[r_singletons]))
break_classes <- class(obj)
if (! inherits(obj, "breaks")) break_classes <- c("breaks", break_classes)
structure(obj, left = left, class = break_classes)
}
create_extended_breaks <- function (obj, x, extend, left, close_end) {
brks <- create_lr_breaks(obj = obj, left = left)
extend_and_close(breaks = brks, x = x, extend = extend, left = left,
close_end = close_end)
}
create_lr_breaks <- function (obj, left) {
assert_that(is.flag(left))
left_vec <- rep(left, length(obj))
st <- singletons(obj)
left_vec[which(st)] <- TRUE
left_vec[which(st) + 1] <- FALSE
create_breaks(obj, left_vec)
}
empty_breaks <- function () {
create_breaks(c(-Inf, Inf), c(TRUE, FALSE))
}
#' Extend `breaks` to the left or right according to `extend` parameter,
#' and close end according to `close_end` parameter
#'
#' @param breaks,x,extend,left,close_end All passed in from `chop()` via
#' a `brk_` inner function
#'
#' @return A `breaks` object.
#' @noRd
extend_and_close <- function (breaks, x, extend, left, close_end) {
extend_flags <- needs_extend(breaks, x, extend, left, close_end)
if ((extend_flags & LEFT) > 0) {
breaks <- extend_endpoint_left(breaks, x, extend)
}
if ((extend_flags & RIGHT) > 0) {
breaks <- extend_endpoint_right(breaks, x, extend)
}
breaks <- maybe_close_end(breaks, left = left, close_end = close_end)
return(breaks)
}
NEITHER <- as.raw(0)
LEFT <- as.raw(1)
RIGHT <- as.raw(2)
BOTH <- LEFT | RIGHT
#' Reports if `breaks` will/should be extended.
#'
#' @param breaks A breaks object
#' @param x Data
#' @param extend,left,close_end Parameters passed into `chop`
#'
#' @return Returns LEFT or RIGHT or BOTH only if `breaks` *will*/*must* be
#' extended i.e. gain an extra break, on the respective sides.
#'
#' @details
#' If `extend` is `FALSE`, always returns `NEITHER`. If `breaks` is length
#' zero, always returns `BOTH`.
#'
#' If extend is `NULL` then `left` and `close_end` are taken into account.
#'
#' To test whether `breaks` will be extended on either side, use
#' `(needs & LEFT) > 0` or `(needs & RIGHT) > 0`.
#'
#' @noRd
needs_extend <- function (breaks, x, extend, left, close_end) {
if (! is.null(extend) && ! extend) return(NEITHER)
if (length(breaks) < 1L) return(BOTH)
needs <- NEITHER
# temporarily close the breaks, to see if unextended closed breaks need
# extension
breaks <- maybe_close_end(breaks, left = left, close_end = close_end)
left_vec <- attr(breaks, "left")
res <- santoku_cast_common(x, unclass_breaks(breaks))
x <- res[[1]]
breaks <- res[[2]]
min_x <- quiet_min(x)
max_x <- quiet_max(x)
if (
isTRUE(extend) ||
min_x < min(breaks) ||
(! left_vec[1] && min_x == min(breaks))
) {
# "... and if ..." the first break is finite, or will be left-open
if (is_gt_minus_inf(breaks[1]) || ! left_vec[1]) {
needs <- needs | LEFT
}
}
if (
isTRUE(extend) ||
max_x > max(breaks) ||
(left_vec[length(left_vec)] && max_x == max(breaks))
) {
# "... and if ..." the last break is finite, or will be left-closed (right-open)
if (is_lt_inf(breaks[length(breaks)]) || left_vec[length(left_vec)]) {
needs <- needs | RIGHT
}
}
return(needs)
}
#' Close end of breaks if close_end is TRUE
#'
#' This never adds a break, it just changes the breaks' `left` attribute.
#' It leaves everything unchanged if `close_end` is `FALSE`.
#'
#' @param breaks,left,close_end Passed in from a `brk_` function
#'
#' @return New breaks object, with the end perhaps closed
#' @noRd
maybe_close_end <- function (breaks, left, close_end) {
if (! close_end) return(breaks)
left_vec <- attr(breaks, "left")
if (left) {
left_vec[length(left_vec)] <- FALSE
} else {
left_vec[1] <- TRUE
}
attr(breaks, "left") <- left_vec
return(breaks)
}
#' Extend the left endpoint of a breaks object according to user parameters
#'
#' This always adds a new break, which is `-Inf` if `extend` is `TRUE`
#' and equal to the minimum of `x` if `extend` is `NULL`.
#'
#' It fixes the `left` attribute if a new singleton break is going to be
#' created.
#'
#' @param breaks,x,extend Passed in from a `brk_` inner function
#'
#' @return A new breaks object
#' @noRd
extend_endpoint_left <- function (breaks, x, extend) {
left <- attr(breaks, "left")
q <- quiet_min(x)
# non-finite q could be Inf if x is empty. Not appropriate for a left endpoint!
extra_break <- if (is.null(extend) && is_gt_minus_inf(q)) q else class_bounds(x)[1]
res <- santoku_cast_common(extra_break, unclass_breaks(breaks))
breaks <- vctrs::vec_c(res[[1]], res[[2]])
# Ensure that a new "singleton" break has the right TRUE,FALSE left-closed
# pattern
if (length(breaks) > 1 && breaks[1] == breaks[2]) {
left[1] <- FALSE
}
breaks <- create_breaks(breaks, c(TRUE, left))
breaks
}
#' Extend the right endpoint of a breaks object according to user parameters
#'
#' This always adds a new break, which is `Inf` if `extend` is `TRUE`
#' and equal to the maximum of `x` if `extend` is `NULL`.
#'
#' It fixes the `left` attribute if a new singleton break is going to be
#' created.
#'
#' @param breaks,x,extend Passed in from a `brk_` inner function
#'
#' @return A new breaks object
#' @noRd
extend_endpoint_right <- function (breaks, x, extend) {
left <- attr(breaks, "left")
q <- quiet_max(x)
extra_break <- if (is.null(extend) && is_lt_inf(q)) q else class_bounds(x)[2]
# necessary because min() and max() may unclass things
res <- santoku_cast_common(unclass_breaks(breaks), extra_break)
breaks <- vctrs::vec_c(res[[1]], res[[2]])
lb <- length(breaks)
# Ensure that a new "singleton" break has the right TRUE,FALSE left-closed
# pattern
if (lb > 1 && breaks[lb] == breaks[lb - 1]) {
left[length(left)] <- TRUE
}
breaks <- create_breaks(breaks, c(left, FALSE))
breaks
}
is_lt_inf <- function (x) {
x <- tryCatch(strict_as_numeric(x),
error = function (...) return(TRUE)
)
x < Inf
}
is_gt_minus_inf <- function (x) {
x <- tryCatch(strict_as_numeric(x),
error = function (...) return(TRUE)
)
x > -Inf
}
#' Return the infimum and supremum of a class
#'
#' The default tries to cast `c(-Inf, Inf)` to the
#' class. If this fails, it returns `c(min(x), max(x))`
#' and emits a warning.
#'
#' @param x Only used for its class
#'
#' @return A length-two object
#' @noRd
class_bounds <- function (x) {
UseMethod("class_bounds")
}
#' @export
class_bounds.numeric <- function (x) c(-Inf, Inf)
#' @export
class_bounds.POSIXct <- function (x) {
as.POSIXct(c(-Inf, Inf), origin = "1970-01-01")
}
#' @export
class_bounds.Date <- function (x) {
as.Date(c(-Inf, Inf), origin = "1970-01-01")
}
#' @export
class_bounds.difftime <- function (x) {
as.difftime(c(-Inf, Inf), units = units(x))
}
#' @export
class_bounds.units <- function (x) {
loadNamespace("units")
# note: the units() call is from namespace base, not units
units::set_units(c(-Inf, Inf), units(x), mode = "standard")
}
#' @export
class_bounds.integer64 <- function (x) {
loadNamespace("bit64")
bit64::lim.integer64()
}
#' @export
class_bounds.zoo <- function (x) {
loadNamespace("zoo")
zoo::zoo(c(-Inf, Inf))
}
#' @export
class_bounds.default <- function (x) {
tryCatch(
vctrs::vec_cast(c(-Inf, Inf), x),
error = function(...) {
warning("Class '", paste(class(x), collapse = "', '"),
"' has no natural endpoints corresponding to +/-Inf for `extend = TRUE`;")
c(quiet_min(x), quiet_max(x))
}
)
}
#' Removes the "breaks" class, and all subclasses, from a break object
#'
#' @param breaks A breaks object
#'
#' @return The object, with any remaining (super)classes
#' @noRd
unclass_breaks <- function (breaks) {
assert_that(is.breaks(breaks))
class_pos <- inherits(breaks, "breaks", which = TRUE)
superclasses <- class(breaks)[-seq_len(class_pos)]
class(breaks) <- if (length(superclasses) == 0 ) {
NULL
} else {
superclasses
}
# this helps vec_cast_common deal with unusual types of breaks
attr(breaks, "left") <- NULL
breaks
}
#' @export
as.double.breaks <- function (x, ...) {
as.double(unclass_breaks(x), ...)
}
#' Return a sequence of width `width`
#'
#' @param width An object representing a width
#' @param start Element to start from
#' @param until Result must be just long enough to cover this element
#'
#' @return A sequence of breaks
#' @noRd
sequence_width <- function(width, start, until) {
UseMethod("sequence_width")
}
#' @export
sequence_width.default <- function (width, start, until) {
breaks <- seq(start, until, width)
too_short <- if (sign(width) > 0) {
breaks[length(breaks)] < until
} else {
breaks[length(breaks)] > until
}
# length(breaks) == 1L captures when start == max_x
if (too_short || length(breaks) == 1L) {
breaks <- c(breaks, breaks[length(breaks)] + width)
}
breaks
}
#' @export
sequence_width.Period <- function(width, start, until) {
loadNamespace("lubridate")
if (as.numeric(until - start) %% as.numeric(width) != 0 || until == start) {
# extend to cover all data / ensure at least one interval
until <- lubridate::add_with_rollback(until, width)
}
# alternative to seq, using Period arithmetic
# We find the number n of widths that gets beyond seq_end
# and add (width * 0:n) to start
# normally this would be ceiling((seq_end - start)/width)
# we calculate it roughly using a Duration
n_intervals <- ceiling((until - start)/lubridate::as.duration(width))
breaks <- lubridate::add_with_rollback(start, (seq(0, n_intervals) * width))
last_break <- breaks[length(breaks)]
too_short <- if (width > 0) {
last_break < until
} else {
last_break > until
}
if (too_short) {
breaks <- c(breaks, lubridate::add_with_rollback(last_break, width))
}
breaks
}
================================================
FILE: R/breaks-misc.R
================================================
#' Create a `breaks` object manually
#'
#' @param breaks A vector, which must be sorted.
#' @param left_vec A logical vector, the same length as `breaks`.
#' Specifies whether each break is left-closed or right-closed.
#'
#' @inherit breaks-doc return
#'
#' @details
#'
#' All breaks must be closed on exactly one side, like `..., x) [x, ...`
#' (left-closed) or `..., x) [x, ...` (right-closed).
#'
#' For example, if `breaks = 1:3` and `left = c(TRUE, FALSE, TRUE)`, then the
#' resulting intervals are \preformatted{
#' T F T
#' [ 1, 2 ] ( 2, 3 )
#' }
#'
#' Singleton breaks are created by repeating a number in `breaks`. Singletons
#' must be closed on both sides, so if there is a repeated number
#' at indices `i`, `i+1`, `left[i]` *must* be `TRUE` and `left[i+1]` must be
#' `FALSE`.
#'
#' `brk_manual()` ignores `left` and `close_end` arguments passed in
#' from [chop()], since `left_vec` sets these manually.
#' `extend` and `drop` arguments are respected as usual.
#'
#' @export
#'
#' @examples
#' lbrks <- brk_manual(1:3, rep(TRUE, 3))
#' chop(1:3, lbrks, extend = FALSE)
#'
#' rbrks <- brk_manual(1:3, rep(FALSE, 3))
#' chop(1:3, rbrks, extend = FALSE)
#'
#' brks_singleton <- brk_manual(
#' c(1, 2, 2, 3),
#' c(TRUE, TRUE, FALSE, TRUE))
#'
#' chop(1:3, brks_singleton, extend = FALSE)
#'
brk_manual <- function (breaks, left_vec) {
assert_that(
is.numeric(breaks),
noNA(breaks),
is.logical(left_vec),
noNA(left_vec),
length(left_vec) == length(breaks)
)
function (x, extend, left, close_end) {
if (! left) warning("Ignoring `left` with `brk_manual()`")
if (! close_end) warning("Ignoring `close_end` with `brk_manual()`")
breaks <- create_breaks(breaks, left_vec)
breaks <- extend_and_close(breaks, x, extend, left = TRUE, close_end = FALSE)
breaks
}
}
#' @rdname chop_fn
#' @export
#' @order 2
brk_fn <- function (fn, ...) {
assert_that(is.function(fn))
function (x, extend, left, close_end) {
breaks <- fn(x, ...)
# some functions (e.g. quantile()) return a named vector
# which might create surprise labels:
breaks <- unname(breaks)
assert_that(is.numeric(breaks))
if (length(breaks) == 0) {
return(empty_breaks())
}
breaks <- create_extended_breaks(breaks, x, extend, left, close_end)
breaks
}
}
#' @rdname chop_pretty
#'
#' @export
#' @order 2
brk_pretty <- function (n = 5, ...) {
assert_that(is.count(n))
function (x, extend, left, close_end) {
breaks <- base::pretty(x, n = n, ...)
if (length(breaks) == 0 || is.null(breaks)) {
return(empty_breaks())
}
breaks <- create_extended_breaks(breaks, x, extend, left, close_end)
breaks
}
}
#' @rdname chop_mean_sd
#' @export
#' @order 2
#' @importFrom lifecycle deprecated
brk_mean_sd <- function (sds = 1:3, sd = deprecated()) {
if (lifecycle::is_present(sd)) {
lifecycle::deprecate_warn(
when = "0.7.0",
what = "brk_mean_sd(sd)",
with = "brk_mean_sd(sds = 'vector of sds')"
)
assert_that(is.number(sd), sd > 0)
# we start from 0 but remove the 0
# this works for e.g. sd = 0.5, whereas seq(1L, sd, 1L) would not:
sds <- seq(0L, sd, 1L)[-1]
if (! sd %in% sds) sds <- c(sds, sd)
}
assert_that(is.numeric(sds), all(sds > 0))
function (x, extend, left, close_end) {
x_mean <- mean(x, na.rm = TRUE)
x_sd <- stats::sd(x, na.rm = TRUE)
if (is.na(x_mean) || is.na(x_sd) || x_sd == 0) {
return(empty_breaks())
}
# add negative sds, then scale them by mean and sd
sds <- sort(sds)
sds <- c(-rev(sds), 0, sds)
breaks <- sds * x_sd + x_mean
breaks <- create_lr_breaks(breaks, left)
needs <- needs_extend(breaks, x, extend, left, close_end)
if ((needs & LEFT) > 0) sds <- c(-Inf, sds)
if ((needs & RIGHT) > 0) sds <- c(sds, Inf)
breaks <- extend_and_close(breaks, x, extend, left, close_end)
class(breaks) <- c("sdBreaks", class(breaks))
attr(breaks, "scaled_endpoints") <- sds
breaks
}
}
================================================
FILE: R/breaks.R
================================================
#' @param breaks A numeric vector.
#' @name breaks-doc
#' @return A function which returns an object of class `breaks`.
NULL
#' Create a standard set of breaks
#'
#' @inherit breaks-doc params return
#' @export
#'
#' @examples
#'
#' chop(1:10, c(2, 5, 8))
#' chop(1:10, brk_default(c(2, 5, 8)))
#'
brk_default <- function (breaks) {
assert_that(noNA(breaks))
function (x, extend, left, close_end) {
create_extended_breaks(breaks, x, extend, left, close_end)
}
}
#' @rdname chop_spikes
#' @export
#' @order 2
brk_spikes <- function (breaks, n = NULL, prop = NULL) {
assert_that(
is.number(n) || is.number(prop),
is.null(n) || is.null(prop),
msg = "exactly one of `n` and `prop` must be a scalar numeric"
)
assert_that(
# it's ok for one of these to be null
n >= 0 || prop >= 0
)
if (! is.function(breaks)) breaks <- brk_default(breaks)
function (x, extend, left, close_end) {
breaks <- breaks(x, extend, left, close_end)
break_elements <- unclass_breaks(breaks)
left_vec <- attr(breaks, "left")
spikes <- find_spikes(x, n, prop)
# We sort spikes in decreasing order so that when we add elements,
# earlier elements remain in place.
spikes <- sort(spikes, decreasing = TRUE)
for (spike in spikes) {
# We could use match() here to go faster, or even put it outside the loop.
match_location <- which(spike == break_elements)
n_matches <- length(match_location)
# If two break elements match the spike, it's already a singleton:
# we don't need to do anything.
if (n_matches >= 2L) next
if (n_matches == 1L) {
# We turn the single matching break into a singleton and make sure
# that left is c(TRUE, FALSE)
break_elements <- append(break_elements, spike, after = match_location)
left_vec <- append(left_vec, FALSE, after = match_location)
left_vec[match_location] <- TRUE
} else {
# We add a singleton break at `spike`
insert_location <- quiet_max(which(spike > break_elements))
if (insert_location <= 0) insert_location <- 0
break_elements <- append(break_elements, rep(spike, 2),
after = insert_location)
left_vec <- append(left_vec, c(TRUE, FALSE), after = insert_location)
}
}
create_breaks(break_elements, left = left_vec)
}
}
#' Class representing a set of intervals
#'
#' @param x A breaks object
#' @param ... Unused
#'
#' @name breaks-class
NULL
#' @rdname breaks-class
#' @export
format.breaks <- function (x, ...) {
if (length(x) < 2) return("Breaks object: no complete intervals")
paste0("Breaks object: ", paste(lbl_intervals()(x), collapse = " "))
}
#' @rdname breaks-class
#' @export
print.breaks <- function (x, ...) cat(format(x, ...))
#' @rdname breaks-class
#' @export
is.breaks <- function (x, ...) inherits(x, "breaks")
on_failure(is.breaks) <- function (call, env) {
paste0(deparse(call$x), " is not an object of class `breaks`")
}
================================================
FILE: R/categorize.R
================================================
#' Categorize `x` according to breaks
#'
#' @param x A vector of data
#' @param breaks A breaks object
#'
#' @return A set of vector codes
#' @noRd
categorize <- function (x, breaks) {
# we first cast to the most informative common type. Then to numeric.
left <- attr(breaks, "left")
res <- santoku_cast_common(x, unclass_breaks(breaks))
# vec_cast won't accept e.g. characters but it also won't convert e.g. Dates
# as.numeric accepts both
# We want to convert things to numeric objects, but NB, not all
# numeric objects will work OK in categorize_impl
x <- tryCatch(strict_as_numeric(res[[1]]),
error = function (...) res[[1]]
)
breaks <- tryCatch(strict_as_numeric(res[[2]]),
error = function (...) res[[2]]
)
# we use is_bare_numeric here because e.g. large integer64 vectors will
# fail in categorize_impl()
codes <- if (rlang::is_bare_numeric(x) && rlang::is_bare_numeric(breaks)) {
categorize_impl(x, breaks, left)
} else {
categorize_non_numeric(x, breaks, left)
}
codes
}
categorize_non_numeric <- function (x, breaks, left) {
if (is.character(x) || is.character(breaks)) {
if (getOption("santoku.warn_character", TRUE)) {
warning_statement <- paste(
"`x` or `breaks` is of type character, using lexical sorting.",
"To turn off this warning, run:",
" options(santoku.warn_character = FALSE)",
collapse = "\n")
warning(warning_statement)
}
}
codes <- rep(NA_integer_, length(x))
for (j in seq_len(length(breaks) - 1)) {
more_than_j <- x > breaks[j]
less_than_j_plus_one <- x < breaks[j+1]
equals_j <- x == breaks[j]
equals_j_plus_one <- x == breaks[j+1]
codes[more_than_j & less_than_j_plus_one] <- j
if (left[j]) codes[equals_j] <- j
if (! left[j+1]) codes[equals_j_plus_one] <- j
}
codes
}
================================================
FILE: R/chop-by-group-size.R
================================================
#' Chop by quantiles
#'
#' `chop_quantiles()` chops data by quantiles.
#' `chop_deciles()` is a convenience function which chops into deciles.
#'
#' @param probs A vector of probabilities for the quantiles. If `probs` has
#' names, these will be used for labels.
#' @param ... For `chop_quantiles`, passed to [chop()]. For `brk_quantiles()`,
#' passed to [stats::quantile()] or [Hmisc::wtd.quantile()].
#' @param weights `NULL` or numeric vector of same length as `x`. If not
#' `NULL`, [Hmisc::wtd.quantile()] is used to calculate weighted quantiles.
#' @param recalc_probs Logical. Recalculate probabilities of quantiles using
#' [`ecdf(x)`][stats::ecdf()]? See below.
#'
#' @inheritParams chop
#' @inherit chop-doc params return
#'
#' @details
#' For non-numeric `x`, `left` is set to `FALSE` by default. This works better
#' for calculating "type 1" quantiles, since they round down. See
#' [stats::quantile()].
#'
#' By default, `chop_quantiles()` shows the requested probabilities in the
#' labels. To show the numeric quantiles themselves, set `raw = TRUE`.
#'
#' When `x` contains duplicates, consecutive quantiles may be the same number. If
#' so, interval labels may be misleading, and if `recalc_probs = FALSE` a warning is
#' emitted. Set `recalc_probs = TRUE` to recalculate the probabilities of the quantiles
#' using the [empirical cumulative distribution function][stats::ecdf()] of `x`.
#' Doing so may give you different labels from what you expect, and will
#' remove any names from `probs`, but it never changes the actual
#' quantiles used for breaks. At present, `recalc_probs = TRUE` is incompatible
#' with non-null `weights`. See the example below.
#'
#' @family chopping functions
#'
#' @export
#' @order 1
#'
#' @examples
#' chop_quantiles(1:10, 1:3/4)
#'
#' chop_quantiles(1:10, c(Q1 = 0, Q2 = 0.25, Q3 = 0.5, Q4 = 0.75))
#'
#' chop(1:10, brk_quantiles(1:3/4))
#'
#' chop_deciles(1:10)
#'
#' # to label by the quantiles themselves:
#' chop_quantiles(1:10, 1:3/4, raw = TRUE)
#'
#' # duplicate quantiles:
#' x <- c(1, 1, 1, 2, 3)
#' quantile(x, 1:5/5)
#' tab_quantiles(x, 1:5/5)
#' tab_quantiles(x, 1:5/5, recalc_probs = TRUE)
chop_quantiles <- function(
x,
probs,
...,
labels = if (raw) lbl_intervals() else
lbl_intervals(single = NULL),
left = is.numeric(x),
raw = FALSE,
weights = NULL,
recalc_probs = FALSE
) {
chop(x, brk_quantiles(probs, weights = weights, recalc_probs = recalc_probs),
labels = labels, ..., left = left, raw = raw)
}
#' @rdname chop_quantiles
#' @export
#' @order 1
chop_deciles <- function(x, ...) {
chop_quantiles(x, 0:10/10, ...)
}
#' Chop equal-sized groups
#'
#' `chop_equally()` chops `x` into groups with an equal number of elements.
#'
#' @param groups Number of groups.
#' @inheritParams chop
#' @inherit chop-doc params return
#'
#' @details
#' `chop_equally()` uses [brk_quantiles()] under the hood. If `x` has duplicate
#' elements, you may get fewer `groups` than requested. If so, a warning will
#' be emitted. See the examples.
#'
#' @family chopping functions
#'
#' @export
#' @order 1
#' @examples
#' chop_equally(1:10, 5)
#'
#' # You can't always guarantee equal-sized groups:
#' dupes <- c(1, 1, 1, 2, 3, 4, 4, 4)
#' quantile(dupes, 0:4/4)
#' chop_equally(dupes, 4)
#' # Or as many groups as you ask for:
#' chop_equally(c(1, 1, 2, 2), 3)
chop_equally <- function (
x,
groups,
...,
labels = lbl_intervals(),
left = is.numeric(x),
raw = TRUE
) {
chop(x, brk_equally(groups), ..., labels = labels, left = left, raw = raw)
}
#' Chop into fixed-sized groups
#'
#' `chop_n()` creates intervals containing a fixed number of elements.
#'
#' @param n Integer. Number of elements in each interval.
#' @inheritParams chop
#' @param tail String. What to do if the final interval has fewer than `n` elements?
#' `"split"` to keep it separate. `"merge"` to merge it with the neighbouring
#' interval.
#' @inherit chop-doc params return
#'
#'
#' @details
#'
#' The algorithm guarantees that intervals contain no more than `n` elements, so
#' long as there are no duplicates in `x` and `tail = "split"`. It also
#' guarantees that intervals contain no fewer than `n` elements, except possibly
#' the last interval (or first interval if `left` is `FALSE`).
#'
#' To ensure that all intervals contain at least `n` elements (so long as there
#' are at least `n` elements in `x`!) set `tail = "merge"`.
#'
#' If `tail = "split"` and there are intervals containing duplicates with more
#' than `n` elements, a warning is given.
#'
#' @export
#' @order 1
#' @family chopping functions
#' @examples
#' chop_n(1:10, 5)
#'
#' chop_n(1:5, 2)
#' chop_n(1:5, 2, tail = "merge")
#'
#' # too many duplicates
#' x <- rep(1:2, each = 3)
#' chop_n(x, 2)
#'
chop_n <- function (
x,
n,
...,
tail = "split"
) {
res <- chop(x, brk_n(n, tail = tail), ...)
if (tail == "split" && max(tabulate(res)) > n) {
warning("Some intervals contain more than ", n, " elements")
}
res
}
================================================
FILE: R/chop-by-width.R
================================================
#' Chop into fixed-width intervals
#'
#' `chop_width()` chops `x` into intervals of fixed `width`.
#'
#' @param width Width of intervals.
#' @param start Starting point for intervals. By default the smallest
#' finite `x` (largest if `width` is negative).
#' @inheritParams chop
#' @inherit chop-doc params return
#'
#' @details
#' If `width` is negative, `chop_width()` sets `left = FALSE` and intervals will
#' go downwards from `start`.
#'
#' @family chopping functions
#' @seealso [brk_width-for-datetime]
#'
#' @export
#' @order 1
#'
#' @examples
#' chop_width(1:10, 2)
#'
#' chop_width(1:10, 2, start = 0)
#'
#' chop_width(1:9, -2)
#'
#' chop(1:10, brk_width(2, 0))
#'
chop_width <- function (
x,
width,
start,
...,
left = sign(width) > 0
) {
chop(x, brk_width(width, start), ..., left = left)
}
#' Chop into equal-width intervals
#'
#' `chop_evenly()` chops `x` into `intervals` intervals of equal width.
#'
#' @param intervals Integer: number of intervals to create.
#' @inheritParams chop
#' @inherit chop-doc params return
#'
#' @family chopping functions
#'
#' @export
#' @order 1
#' @examples
#' chop_evenly(0:10, 5)
#'
chop_evenly <- function (
x,
intervals,
...
) {
chop(x, brk_evenly(intervals), ...)
}
#' Chop into proportions of the range of x
#'
#' `chop_proportions()` chops `x` into `proportions` of its range, excluding
#' infinite values.
#'
#' By default, labels show the raw numeric endpoints. To label intervals by
#' the proportions, use `raw = FALSE`.
#'
#' @param proportions Numeric vector between 0 and 1: proportions of x's range.
#' If `proportions` has names, these will be used for labels.
#' @inheritParams chop
#' @inherit chop-doc params return
#'
#' @export
#' @order 1
#' @family chopping functions
#' @examples
#' chop_proportions(0:10, c(0.2, 0.8))
#' chop_proportions(0:10, c(Low = 0, Mid = 0.2, High = 0.8))
#'
chop_proportions <- function (
x,
proportions,
...,
raw = TRUE
) {
chop(x, brk_proportions(proportions), ..., raw = raw)
}
================================================
FILE: R/chop-isolates.R
================================================
#' Chop common values into singleton intervals
#'
#' `chop_spikes()` lets you chop common values of `x` into their own
#' singleton intervals. This can help make unusual values visible.
#'
#' This function is `r lifecycle::badge("experimental")`.
#'
#' @param breaks A numeric vector of cut-points or a call to a `brk_*` function.
#' The resulting [`breaks`][breaks-class] object will be modified to add
#' singleton breaks.
#' @param n,prop Scalar. Provide either `n`, a number of values, or `prop`,
#' a proportion of `length(x)`. Values of `x` which occur at least this
#' often will get their own singleton break.
#' @inheritParams chop
#' @inherit chop-doc params return
#'
#' @export
#' @order 1
#' @family chopping functions
#' @seealso [dissect()] for a different approach.
#' @examples
#' x <- c(1:4, rep(5, 5), 6:10)
#' chop_spikes(x, c(2, 7), n = 5)
#' chop_spikes(x, c(2, 7), prop = 0.25)
#' chop_spikes(x, brk_width(5), n = 5)
#'
#' set.seed(42)
#' x <- runif(40, 0, 10)
#' x <- sample(x, 200, replace = TRUE)
#' tab_spikes(x, brk_width(2, 0), prop = 0.05)
chop_spikes <- function (
x,
breaks,
...,
n = NULL,
prop = NULL
) {
chop(x, brk_spikes(breaks, n = n, prop = prop), ...)
}
#' Cut data into intervals, separating out common values
#'
#' Sometimes it's useful to separate out common elements of `x`.
#' `dissect()` chops `x`, but puts common elements of `x` ("spikes")
#' into separate categories.
#'
#' Unlike [chop_spikes()], `dissect()` doesn't break up
#' intervals which contain a spike. As a result, unlike `chop_*` functions,
#' `dissect()` does not chop `x` into disjoint intervals. See the examples.
#'
#' If breaks are data-dependent, their labels may be misleading after common
#' elements have been removed. See the example below. To get round this,
#' set `exclude_spikes` to `TRUE`. Then breaks will be calculated after
#' removing spikes from the data.
#'
#' Levels of the result are ordered by the minimum element in each level. As
#' a result, if `drop = FALSE`, empty levels will be placed last.
#'
#' This function is `r lifecycle::badge("experimental")`.
#'
#' @param x,breaks,... Passed to [chop()].
#' @inheritParams chop_spikes
#' @param spike_labels [Glue][glue::glue()] string for spike labels. Use `"{l}"`
#' for the spike value.
#' @param exclude_spikes Logical. Exclude spikes before chopping `x`? This
#' can affect the location of data-dependent breaks.
#'
#' @return
#' `dissect()` returns the result of [chop()], but with common values put into
#' separate factor levels.
#'
#' `tab_dissect()` returns a contingency [table()][base::table].
#'
#' @seealso [chop_spikes()] for a different approach.
#' @export
#' @order 1
#'
#' @examples
#' x <- c(2, 3, 3, 3, 4)
#' dissect(x, c(2, 4), n = 3)
#' dissect(x, brk_width(2), prop = 0.5)
#'
#' set.seed(42)
#' x <- runif(40, 0, 10)
#' x <- sample(x, 200, replace = TRUE)
#' # Compare:
#' table(dissect(x, brk_width(2, 0), prop = 0.05))
#' # Versus:
#' tab_spikes(x, brk_width(2, 0), prop = 0.05)
#'
#' # Potentially confusing data-dependent breaks:
#' set.seed(42)
#' x <- rnorm(99)
#' x[1:9] <- x[1]
#' tab_quantiles(x, 1:2/3)
#' tab_dissect(x, brk_quantiles(1:2/3), n = 9)
#' # Calculate quantiles excluding spikes:
#' tab_dissect(x, brk_quantiles(1:2/3), n = 9, exclude_spikes = TRUE)
dissect <- function (x,
breaks,
...,
n = NULL,
prop = NULL,
spike_labels = "{{{l}}}",
exclude_spikes = FALSE) {
assert_that(
is.number(n) || is.number(prop),
is.null(n) || is.null(prop),
is.string(spike_labels),
is.flag(exclude_spikes),
msg = "exactly one of `n` and `prop` must be a scalar numeric"
)
assert_that(
# it's ok for one of these to be null
n >= 0 || prop >= 0
)
spikes <- find_spikes(x, n, prop)
x_spikes <- match(x, spikes)
is_spike <- ! is.na(x_spikes)
x_spikes <- x_spikes[is_spike]
if (exclude_spikes) {
x_not_spikes <- x[! is_spike]
chopped_not_spikes <- chop(x_not_spikes, breaks, ...)
chopped <- factor(rep(NA_integer_, length(x)),
levels = levels(chopped_not_spikes))
chopped[! is_spike] <- chopped_not_spikes
} else {
chopped <- chop(x, breaks, ...)
}
elabels <- endpoint_labels(spikes, raw = TRUE)
glue_env <- new.env()
assign("l", elabels, envir = glue_env)
spike_labels <- glue::glue(spike_labels, .envir = glue_env)
new_levels <- c(levels(chopped), spike_labels)
levels(chopped) <- new_levels
chopped[is_spike] <- spike_labels[x_spikes]
# We reorder the levels of chopped in order of their smallest elements.
# Note that if `drop = FALSE`, empty intervals will be at the end.
# The alternative would be to call `breaks` again and get the left endpoints
# but this is complex.
chopped <- stats::reorder(chopped, x, FUN = quiet_min)
attr(chopped, "scores") <- NULL # remove leftover from reorder()
chopped
}
#' Find common elements in `x`
#'
#' @param x A vector
#' @param n Number of elements that counts as common. Specify exactly one of `n`
#' and `prop`.
#' @param prop Proportion of `length(x)` that counts as common
#'
#' @return The common elements, not necessarily in order. NA values are never
#' considered as common.
#' @noRd
find_spikes <- function (x, n, prop) {
n <- n %||% (length(x) * prop)
unique_x <- unique(x)
x_counts <- tabulate(match(x, unique_x))
spikes <- unique_x[x_counts >= n]
spikes <- spikes[! is.na(spikes)]
spikes
}
================================================
FILE: R/chop-misc.R
================================================
#' Chop by standard deviations
#'
#' Intervals are measured in standard deviations on either side of the
#' mean.
#'
#' In version 0.7.0, these functions changed to specifying `sds` as a vector.
#' To chop 1, 2 and 3 standard deviations around the mean, write
#' `chop_mean_sd(x, sds = 1:3)` instead of `chop_mean_sd(x, sd = 3)`.
#'
#' @param sds Positive numeric vector of standard deviations.
#' @param sd `r lifecycle::badge("deprecated")`
#'
#' @inheritParams chop
#' @inherit chop-doc params return
#'
#' @family chopping functions
#'
#' @export
#' @order 1
#'
#' @examples
#' chop_mean_sd(1:10)
#'
#' chop(1:10, brk_mean_sd())
#'
#' @importFrom lifecycle deprecated
chop_mean_sd <- function (
x,
sds = 1:3,
...,
raw = FALSE,
sd = deprecated()
) {
chop(x, brk_mean_sd(sds = sds, sd = sd), ..., raw = raw)
}
#' Chop using pretty breakpoints
#'
#' `chop_pretty()` uses [base::pretty()] to calculate breakpoints
#' which are 1, 2 or 5 times a power of 10. These look nice in graphs.
#'
#' [base::pretty()] tries to return `n+1` breakpoints, i.e. `n` intervals, but
#' note that this is not guaranteed. There are methods for Date and POSIXct
#' objects.
#'
#' For fine-grained control over [base::pretty()] parameters, use
#' `chop(x, brk_pretty(...))`.
#'
#' @inheritParams chop
#' @inherit chop-doc params return
#' @param n Positive integer passed to [base::pretty()]. How many intervals to chop into?
#' @param ... Passed to [chop()] by `chop_pretty()` and `tab_pretty()`; passed
#' to [base::pretty()] by `brk_pretty()`.
#'
#' @export
#' @order 1
#'
#' @examples
#' chop_pretty(1:10)
#'
#' chop(1:10, brk_pretty(n = 5, high.u.bias = 0))
#'
chop_pretty <- function (x, n = 5, ...) {
chop(x, brk_pretty(n = n), ...)
}
#' Chop using an existing function
#'
#' `chop_fn()` is a convenience wrapper: `chop_fn(x, foo, ...)`
#' is the same as `chop(x, foo(x, ...))`.
#'
#' @param fn A function which returns a numeric vector of breaks.
#' @param ... Further arguments to `fn`
#' @inheritParams chop
#' @inherit chop-doc params return
#'
#' @export
#' @order 1
#' @family chopping functions
#' @examples
#'
#' if (requireNamespace("scales")) {
#' chop_fn(rlnorm(10), scales::breaks_log(5))
#' # same as
#' # x <- rlnorm(10)
#' # chop(x, scales::breaks_log(5)(x))
#' }
#'
chop_fn <- function (
x,
fn,
...,
extend = NULL,
left = TRUE,
close_end = TRUE,
raw = NULL,
drop = TRUE
) {
chop(x, brk_fn(fn, ...), extend = extend, left = left, close_end = close_end,
raw = raw, drop = drop)
}
================================================
FILE: R/chop.R
================================================
#' @name chop-doc
#' @param ... Passed to [chop()].
#' @return
#' `chop_*` functions return a [`factor`][base::factor] of the same length as `x`.
#'
#' `brk_*` functions return a [`function`] to create `breaks`.
#'
#' `tab_*` functions return a contingency [`table`][base::table].
NULL
#' Cut data into intervals
#'
#' `chop()` cuts `x` into intervals. It returns a [`factor`][base::factor] of
#' the same length as `x`, representing which interval contains each element of `x`.
#' `kiru()` is an alias for `chop`.
#' `tab()` calls `chop()` and returns a contingency [`table`][base::table] from
#' the result.
#'
#' @param x A vector.
#' @param breaks A numeric vector of cut-points, or a function to create
#' cut-points from `x`.
#' @param labels A character vector of labels or a function to create labels.
#' @param extend Logical. If `TRUE`, always extend breaks to `+/-Inf`. If `NULL`,
#' extend breaks to `min(x)` and/or `max(x)` only if necessary. If `FALSE`, never
#' extend.
#' @param left Logical. Left-closed or right-closed breaks?
#' @param close_end Logical. Close last break at right? (If `left` is `FALSE`,
#' close first break at left?)
#' @param raw Logical. Use raw values in labels?
#' @param drop Logical. Drop unused levels from the result?
#'
#' @details
#'
#' `x` may be a numeric vector, or more generally, any vector which can be
#' compared with `<` and `==` (see [Ops][groupGeneric]). In particular
#' [Date][base::Dates] and [date-time][DateTimeClasses] objects are supported.
#' Character vectors are supported with a warning.
#'
#' ## Breaks
#'
#' `breaks` may be a vector or a function.
#'
#' If it is a vector, `breaks` gives the interval endpoints. Repeating a value
#' creates a "singleton" interval, which contains only that value.
#' For example `breaks = c(1, 3, 3, 5)` creates 3 intervals:
#' \code{[1, 3)}, \code{{3}} and \code{(3, 5]}.
#'
#' If `breaks` is a function, it is called with the `x`, `extend`, `left` and
#' `close_end` arguments, and should return an object of class `breaks`.
#' Use `brk_*` functions to create a variety of data-dependent breaks.
#'
#' Names of `breaks` may be used for labels. See "Labels" below.
#'
#' ## Options for breaks
#'
#' By default, left-closed intervals are created. If `left` is `FALSE`,
#' right-closed intervals are created.
#'
#' If `close_end` is `TRUE` the final break (or first break if `left` is `FALSE`)
#' will be closed at both ends. This guarantees that all values `x` with
#' `min(breaks) <= x <= max(breaks)` are included in the intervals.
#'
#' Before version 0.9.0, `close_end` was `FALSE` by default, and also behaved
#' differently with respect to extended breaks: see "Extending intervals" below.
#'
#' Using [mathematical set notation][lbl_intervals()]:
#'
#' * If `left` is `TRUE` and `close_end` is `TRUE`, breaks will look like
#' \code{[b1, b2), [b2, b3) ... [b_(n-1), b_n]}.
#' * If `left` is `FALSE` and `close_end` is `TRUE`, breaks will look like
#' \code{[b1, b2], (b2, b3] ... (b_(n-1), b_n]}.
#' * If `left` is `TRUE` and `close_end` is `FALSE`, all breaks will look like
#' \code{... [b1, b2) ...}.
#' * If `left` is `FALSE` and `close_end` is `FALSE`, all breaks will look like
#' \code{... (b1, b2] ...}.
#'
#' ## Extending intervals
#'
#' If `extend` is `TRUE`, intervals will be extended to \code{[-Inf,
#' min(breaks))} and \code{(max(breaks), Inf]}.
#'
#' If `extend` is `NULL` (the default), intervals will be extended to
#' \code{[min(x), min(breaks))} and \code{(max(breaks), max(x)]}, only if
#' necessary, i.e. only if elements of `x` would be outside the unextended
#' breaks.
#'
#' If `extend` is `FALSE`, intervals are never extended.
#'
#' Note that even when `extend = TRUE`, extended intervals will be
#' dropped from the factor levels if they contain no elements and `drop = TRUE`.
#'
#' `close_end` is only relevant if intervals are not extended;
#' extended intervals are always closed on the outside. This is a change from
#' previous behaviour. Up to version 0.8.0, `close_end` was applied to the
#' last user-specified interval, before any extended intervals were created.
#'
#' Since 1.1.0, infinity is represented as \eqn{\infty}{the infinity symbol}
#' in breaks on unicode platforms. Set `options(santoku.infinity = "Inf")`
#' to get the old behaviour.
#'
#' ## Labels
#'
#' `labels` may be a character vector. It should have the same length as the
#' (possibly extended) number of intervals. Alternatively, `labels` may be a
#' `lbl_*` function such as [lbl_dash()].
#'
#' If `breaks` is a named vector, then names of `breaks` will be
#' used as labels for the interval starting at the corresponding element. This
#' overrides the `labels` argument (but unnamed breaks will still use `labels`).
#' This feature is `r lifecycle::badge("experimental")`.
#'
#' If `labels` is `NULL`, then integer codes will be returned instead of a
#' factor.
#'
#' If `raw` is `TRUE`, labels will show the actual interval endpoints, usually
#' numbers. If `raw` is `FALSE` then labels may show other objects, such
#' as quantiles for [chop_quantiles()] and friends, proportions of the range for
#' [chop_proportions()], or standard deviations for [chop_mean_sd()].
#'
#' If `raw` is `NULL` then `lbl_*` functions will use their default (usually
#' `FALSE`). Otherwise, the `raw` argument to `chop()` overrides `raw` arguments
#' passed into `lbl_*` functions directly.
#'
#'
#' ## Miscellaneous
#'
#' `NA` values in `x`, and values which are outside the extended endpoints,
#' return `NA`.
#'
#' `kiru()` is a synonym for `chop()`. If you load `{tidyr}`, you can use it to
#' avoid confusion with `tidyr::chop()`.
#'
#' Note that `chop()`, like all of R, uses binary arithmetic. Thus, numbers may
#' not be exactly equal to what you think they should be. There is an example
#' below.
#'
#' @return
#' `chop()` returns a [`factor`][base::factor] of the same length as `x`,
#' representing the intervals containing the value of `x`.
#'
#' `tab()` returns a contingency [`table`][base::table].
#'
#' @export
#'
#' @family chopping functions
#'
#' @seealso [base::cut()], [`non-standard-types`] for chopping objects that
#' aren't numbers.
#'
#' @examples
#'
#' chop(1:7, c(2, 4, 6))
#'
#' chop(1:7, c(2, 4, 6), extend = FALSE)
#'
#' # Repeat a number for a singleton break:
#' chop(1:7, c(2, 4, 4, 6))
#'
#' chop(1:7, c(2, 4, 6), left = FALSE)
#'
#' chop(1:7, c(2, 4, 6), close_end = FALSE)
#'
#' chop(1:7, brk_quantiles(c(0.25, 0.75)))
#'
#' # A single break is fine if `extend` is not `FALSE`:
#' chop(1:7, 4)
#'
#' # Floating point inaccuracy:
#' chop(0.3/3, c(0, 0.1, 0.1, 1), labels = c("< 0.1", "0.1", "> 0.1"))
#'
#' # -- Labels --
#'
#' chop(1:7, c(Lowest = 1, Low = 2, Mid = 4, High = 6))
#'
#' chop(1:7, c(2, 4, 6), labels = c("Lowest", "Low", "Mid", "High"))
#'
#' chop(1:7, c(2, 4, 6), labels = lbl_dash())
#'
#' # Mixing names and other labels:
#' chop(1:7, c("<2" = 1, 2, 4, ">=6" = 6), labels = lbl_dash())
#'
#' # -- Non-standard types --
#'
#' chop(as.Date("2001-01-01") + 1:7, as.Date("2001-01-04"))
#'
#' suppressWarnings(chop(LETTERS[1:7], "D"))
#'
#'
chop <- function (x, breaks,
labels = lbl_intervals(),
extend = NULL,
left = TRUE,
close_end = TRUE,
raw = NULL,
drop = TRUE
) {
assert_that(
is.flag(extend) || is.null(extend),
is.flag(left),
is.flag(close_end),
is.flag(drop),
is.flag(raw) || is.null(raw)
)
if (! is.function(breaks)) breaks <- brk_default(breaks)
breaks <- breaks(x, extend, left, close_end)
assert_that(is.breaks(breaks), length(breaks) >= 2L)
codes <- categorize(x, breaks)
if (is.null(labels)) return(codes)
lbls <- if (is.function(labels)) {
if (is.null(raw)) labels(breaks) else labels(breaks, raw = raw)
} else {
labels
}
lbls <- add_break_names(lbls, breaks)
stopifnot(length(lbls) == length(breaks) - 1)
real_codes <- if (drop) unique(codes[! is.na(codes)]) else TRUE
if (anyDuplicated(lbls[real_codes])) {
stop("Duplicate labels found: ", paste(lbls, collapse = ", "))
}
result <- factor(codes, levels = seq.int(length(breaks) - 1L),
labels = lbls)
if (drop) result <- droplevels(result)
return(result)
}
#' @rdname chop
#' @export
kiru <- chop
#' Chop data precisely (for programmers)
#'
#' `fillet()` calls [chop()] with `extend = FALSE` and `drop = FALSE`. This
#' ensures that you get only the `breaks` and `labels` you ask for. When
#' programming, consider using `fillet()` instead of `chop()`.
#'
#' @inheritParams chop
#'
#' @return `fillet()` returns a [`factor`][base::factor] of the same length as
#' `x`, representing the intervals containing the value of `x`.
#'
#' @family chopping functions
#'
#' @export
#'
#' @examples
#' fillet(1:10, c(2, 5, 8))
fillet <- function (
x,
breaks,
labels = lbl_intervals(),
left = TRUE,
close_end = TRUE,
raw = NULL
) {
chop(x, breaks, labels, left = left, close_end = close_end, extend = FALSE,
raw = raw, drop = FALSE)
}
================================================
FILE: R/labels-datetime.R
================================================
#' Parse a `strftime` format string
#'
#' Splits a format string into literal and directive tokens.
#'
#' @param fmt A `strftime` format string.
#'
#' @return A data frame with columns `type` (`"literal"` or `"directive"`)
#' and `token`.
#' @noRd
parse_strftime <- function(fmt) {
assert_that(is.string(fmt))
chars <- strsplit(fmt, "", fixed = TRUE)[[1]]
n <- length(chars)
i <- 1L
types <- character(0)
tokens <- character(0)
while (i <= n) {
if (chars[i] != "%") {
start <- i
while (i <= n && chars[i] != "%") i <- i + 1L
types <- c(types, "literal")
tokens <- c(tokens, paste0(chars[start:(i - 1L)], collapse = ""))
next
}
if (i < n && chars[i + 1L] == "%") {
types <- c(types, "literal")
tokens <- c(tokens, "%")
i <- i + 2L
next
}
start <- i
i <- i + 1L
if (i <= n && chars[i] %in% c("E", "O")) i <- i + 1L
while (i <= n && grepl("[0-9]", chars[i])) i <- i + 1L
if (i <= n) i <- i + 1L
types <- c(types, "directive")
tokens <- c(tokens, paste0(chars[start:(i - 1L)], collapse = ""))
}
data.frame(type = types, token = tokens, stringsAsFactors = FALSE)
}
#' Return relative component rank for a strftime directive
#'
#' Lower values are "greater" components, e.g. year before month before day.
#'
#' @param token A single strftime directive token (e.g. "%Y").
#'
#' @return Numeric rank, or `NA_real_` if unknown.
#' @noRd
strftime_rank <- function(token) {
code <- gsub("^%[EO]?([0-9]*)", "", token)
if (code %in% c("Y", "y", "C", "G", "g")) return(1)
if (code %in% c("m", "b", "B", "h")) return(2)
if (code %in% c("d", "e", "j", "a", "A", "u", "w", "p", "P")) return(3)
if (code %in% c("H", "I", "k", "l", "M", "S", "OS")) return(4)
NA_real_
}
#' Format date/time endpoints into token matrix
#'
#' @param x Date/time-like vector.
#' @param fmt A `strftime` format string.
#' @param spec Parsed strftime spec.
#'
#' @return A character matrix: rows are endpoints, columns are format tokens.
#' @noRd
format_strftime_tokens <- function(x, fmt = NULL, spec = parse_strftime(fmt)) {
n <- length(x)
tokens <- lapply(seq_len(nrow(spec)), function(i) {
if (identical(spec$type[[i]], "literal")) {
rep(spec$token[[i]], n)
} else {
format(x, spec$token[[i]])
}
})
do.call(cbind, tokens)
}
#' Collapse two formatted date/time labels
#'
#' @param l_tokens Left endpoint tokens.
#' @param r_tokens Right endpoint tokens.
#' @param symbol Separator for full ranges.
#'
#' @return A single collapsed range label.
#' @noRd
collapse_datetime_label <- function(
l_tokens,
r_tokens,
spec,
symbol = "-"
) {
spaced_symbol <- paste0(" ", symbol, " ")
full <- paste0(paste0(l_tokens, collapse = ""), spaced_symbol,
paste0(r_tokens, collapse = ""))
ranks <- vapply(spec$token, strftime_rank, FUN.VALUE = numeric(1))
is_directive <- spec$type == "directive"
comparable <- is_directive & !is.na(ranks)
differs <- comparable & (l_tokens != r_tokens)
if (!any(differs)) return(full)
diff_rank <- min(ranks[differs])
collapse_rank <- diff_rank
# If both day and time differ, keep month with both endpoints so that
# day labels remain anchored to a month (issue #58).
if (diff_rank == 3 && any(differs & ranks > diff_rank)) {
collapse_rank <- 2
}
higher_differs <- comparable & (ranks < diff_rank) & (l_tokens != r_tokens)
active_components <- which(comparable & (ranks >= collapse_rank))
if (any(higher_differs) || length(active_components) == 0 || diff_rank == 1) {
return(full)
}
left_end <- max(active_components)
right_start <- min(active_components)
left_part <- paste0(l_tokens[seq_len(left_end)], collapse = "")
right_part <- paste0(r_tokens[right_start:length(r_tokens)], collapse = "")
if (!nzchar(left_part) || !nzchar(right_part)) return(full)
# joiner <- if (diff_rank %in% 3:4) symbol else spaced_symbol
joiner <- if (ranks[right_start] == ranks[left_end]) symbol else spaced_symbol
paste0(left_part, joiner, right_part)
}
#' @rdname lbl_datetime
#' @export
lbl_date <- function(
fmt = "%e %b %Y",
symbol = "-",
unit = as.difftime(1, units = "days"),
single = "{l}",
first = NULL,
last = NULL
) {
lbl_datetime(
fmt = fmt,
symbol = symbol,
unit = unit,
single = single,
first = first,
last = last
)
}
#' Label dates and datetimes
#'
#' @description
#' `r lifecycle::badge("experimental")`
#'
#' `lbl_date()` and `lbl_datetime()` produce nice labels for dates
#' and datetimes. Where possible ranges are simplified, like
#' like "13-14 Jul 2026" or "11:15-12:15 1 Dec 2025".
#'
#' @inherit label-doc
#' @inherit first-last-doc
#' @param symbol String: separator to use for full ranges.
#' @param unit Optional interval unit for non-overlapping labels. If not `NULL`,
#'. endpoints are adjusted in the style of [lbl_discrete()].
#'
#' @family labelling functions
#'
#' @export
#'
#' @examples
#' winter <- as.Date("2025-12-01") + 0:89
#' tab(winter, as.Date(c("2025-12-25", "2026-01-06")),
#' labels = lbl_date())
#' new_year <- as.POSIXct("2025-12-31 23:00") + 0:120 * 60
#' round_midnight <- as.POSIXct(c("2025-12-31 23:59", "2026-01-01 00:05"))
#' tab(new_year, round_midnight,
#' labels = lbl_datetime())
#' tab(new_year, round_midnight,
#' labels = lbl_datetime(unit = as.difftime(1, units = "mins")))
lbl_datetime <- function(
fmt = "%H:%M:%S %b %e %Y",
symbol = "-",
unit = NULL,
single = "{l}",
first = NULL,
last = NULL
) {
assert_that(
is.string(fmt),
is.string(symbol),
length(unit) <= 1L,
is.string(single) || is.null(single),
is.string(first) || is.null(first),
is.string(last) || is.null(last)
)
function(breaks, raw = FALSE) {
assert_that(is.breaks(breaks))
len_breaks <- length(breaks)
endpoints <- scaled_endpoints(breaks, raw = raw)
pieces <- discrete_interval_endpoints(
breaks = breaks,
unit = unit,
endpoints = endpoints
)
l <- pieces$l
r <- pieces$r
is_singleton <- pieces$singletons
too_small <- pieces$too_small
l_closed <- pieces$l_closed
r_closed <- pieces$r_closed
if (any(too_small)) {
warning("Intervals smaller than `unit` are labelled as \"--\"")
}
spec <- parse_strftime(fmt)
l_tokens <- format_strftime_tokens(l, spec = spec)
r_tokens <- format_strftime_tokens(r, spec = spec)
labels <- vapply(seq_len(len_breaks - 1L), function(i) {
collapse_datetime_label(
l_tokens = l_tokens[i, ],
r_tokens = r_tokens[i, ],
spec = spec,
symbol = symbol
)
}, FUN.VALUE = character(1))
l <- apply(l_tokens, 1, paste0, collapse = "")
r <- apply(r_tokens, 1, paste0, collapse = "")
labels[too_small] <- "--"
if (!is.null(single)) {
labels[is_singleton] <- glue::glue(single,
l = l[is_singleton],
r = r[is_singleton],
l_closed = l_closed[is_singleton],
r_closed = r_closed[is_singleton]
)
}
if (!is.null(first)) {
labels[1] <- glue::glue(first, l = l[1], r = r[1],
l_closed = l_closed[1], r_closed = r_closed[1]
)
}
if (!is.null(last)) {
ll <- len_breaks - 1L
labels[ll] <- glue::glue(last, l = l[ll], r = r[ll],
l_closed = l_closed[ll], r_closed = r_closed[ll]
)
}
labels
}
}
================================================
FILE: R/labels-glue.R
================================================
#' Label chopped intervals using the `glue` package
#'
#' Use `"{l}"` and `"{r}"` to show the left and right endpoints of the intervals.
#'
#' @inherit label-doc
#' @inherit first-last-doc params
#' @param label A glue string passed to [glue::glue()].
#' @param ... Further arguments passed to [glue::glue()].
#'
#' @details
#'
#' The following variables are available in the glue string:
#'
#' * `l` is a character vector of left endpoints of intervals.
#' * `r` is a character vector of right endpoints of intervals.
#' * `l_closed` is a logical vector. Elements are `TRUE` when the left
#' endpoint is closed.
#' * `r_closed` is a logical vector, `TRUE` when the right endpoint is closed.
#'
#' Endpoints will be formatted by `fmt` before being passed to `glue()`.
#'
#' @family labelling functions
#'
#' @export
#'
#' @examples
#' tab(1:10, c(1, 3, 3, 7),
#' labels = lbl_glue("{l} to {r}", single = "Exactly {l}"))
#'
#' tab(1:10 * 1000, c(1, 3, 5, 7) * 1000,
#' labels = lbl_glue("{l}-{r}",
#' fmt = function(x) prettyNum(x, big.mark=',')))
#'
#' # reproducing lbl_intervals():
#' interval_left <- "{ifelse(l_closed, '[', '(')}"
#' interval_right <- "{ifelse(r_closed, ']', ')')}"
#' glue_string <- paste0(interval_left, "{l}", ", ", "{r}", interval_right)
#' tab(1:10, c(1, 3, 3, 7), labels = lbl_glue(glue_string, single = "{{{l}}}"))
#'
lbl_glue <- function (
label,
fmt = NULL,
single = NULL,
first = NULL,
last = NULL,
raw = deprecated(),
...
) {
assert_that(
is.string(label),
is.null(fmt) || is_format(fmt),
is.string(first) || is.null(first),
is.string(last) || is.null(last)
)
if (lifecycle::is_present(raw)) {
lifecycle::deprecate_stop("0.9.0", "lbl_glue(raw)", "chop(raw)")
}
function (breaks, raw = FALSE) {
assert_that(is.breaks(breaks))
len_breaks <- length(breaks)
labels <- character(len_breaks - 1)
elabels <- endpoint_labels(breaks, raw = raw, fmt = fmt)
l <- elabels[-len_breaks]
r <- elabels[-1]
left <- attr(breaks, "left")
# Breaks like [1, 2) [2, 3] have
# left TRUE, TRUE, FALSE for breaks 1,2,3
# The first two TRUEs say that the left brackets are closed
# The last two TRUE & FALSE say that the right brackets are open
# and closed respectively. So:
l_closed <- left[-len_breaks]
r_closed <- ! left[-1]
# check ... for anything not in glue::glue args
# effectively, we move any user-supplied arguments into
# an environment specifically for glue
# this is mostly to make the lbl_midpoints() hack
# of passing in `m` work
dots <- rlang::enexprs(...)
glue_env <- new.env()
not_glue_args <- setdiff(names(dots), names(formals(glue::glue)))
for (nm in not_glue_args) {
assign(deparse(dots[[nm]]),
eval(dots[[nm]], parent.frame()),
glue_env
)
}
labels <- glue::glue(label, l = l, r = r, l_closed = l_closed,
r_closed = r_closed, ..., .envir = glue_env)
if (! is.null(single)) {
# which breaks are singletons?
singletons <- singletons(breaks)
labels[singletons] <- glue::glue(single,
l = l[singletons],
r = r[singletons],
l_closed = l_closed[singletons],
r_closed = r_closed[singletons],
...,
.envir = glue_env
)
}
if (! is.null(first)) {
labels[1] <- glue::glue(first, l = l[1], r = r[1],
l_closed = l_closed[1],
r_closed = r_closed[1],
...,
.envir = glue_env
)
}
if (! is.null(last)) {
ll <- len_breaks - 1
labels[ll] <- glue::glue(last, l = l[ll], r = r[ll],
l_closed = l_closed[ll],
r_closed = r_closed[ll],
...,
.envir = glue_env
)
}
return(labels)
}
}
================================================
FILE: R/labels-impl.R
================================================
#' Replaces labels with names from the breaks vector
#'
#' Only non-zero-char names are used.
#'
#' @param labels Passed in from chop, possibly via a `lbl_*` function
#' @param breaks Breaks object created via a `brk_*` function. Some of
#' these preserve names of a given argument (`brk_default()`,
#' `brk_proportions()`, `brk_quantiles()`)
#'
#' @return The altered labels
#' @noRd
#'
add_break_names <- function(labels, breaks) {
if (is.null(names(breaks))) return(labels)
is_named <- nzchar(names(breaks))
# These are possibly-extended breaks; last break is the rightmost endpoint
# and any name is ignored:
is_named[length(is_named)] <- FALSE
break_names_for_labels <- names(breaks)[is_named]
# length(labels) == length(breaks) - 1
is_named <- is_named[-length(is_named)]
labels[is_named] <- break_names_for_labels
return(labels)
}
#' Return formatted strings for endpoints
#'
#' Methods will pick up a `scaled_endpoints`
#' attribute if one exists. This provides the numbers
#' for when `raw = FALSE`.
#'
#' Different breaks subclasses may have different default formats.
#'
#' A `.numeric` method exists so that formatted labels can be created
#' from e.g. midpoints or other things that aren't breaks
#' themselves.
#'
#' @param breaks Either a breaks object, or a numeric vector
#' @param raw Report raw numbers instead of e.g. quantiles?
#' @param fmt Format string or function
#' @param ... Not used
#'
#' @return A character vector of break endpoints.
#' @noRd
#'
endpoint_labels <- function (breaks, raw, fmt = NULL, ...) {
UseMethod("endpoint_labels")
}
#' @export
endpoint_labels.numeric <- function (breaks, raw, fmt = NULL, ...) {
endpoints <- scaled_endpoints(breaks, raw = raw)
elabels <- if (! is.null(fmt)) {
apply_format(fmt, endpoints)
} else {
unique_truncation(endpoints)
}
elabels[is.infinite(endpoints)] <- sub("Inf ?", symbol_infinity(),
elabels[is.infinite(endpoints)])
return(elabels)
}
#' @export
endpoint_labels.integer <- endpoint_labels.numeric
#' @export
endpoint_labels.double <- endpoint_labels.numeric
#' @export
endpoint_labels.default <- function (breaks, raw, fmt = NULL, ...) {
endpoints <- scaled_endpoints(breaks, raw = raw)
elabels <- if (! is.null(fmt)) {
apply_format(fmt, endpoints)
} else {
base::format(endpoints)
}
return(elabels)
}
#' @export
endpoint_labels.Date <- function (breaks, raw, fmt = NULL, ...) {
elabels <- scaled_endpoints(breaks, raw = raw)
# this could be a number. If so, a `fmt` for `sprintf`
# will work fine:
if (! inherits(elabels, "Date")) return(NextMethod())
# set default format
fmt <- fmt %||% "%F"
elabels_chr <- apply_format(fmt, elabels)
minus_inf <- is.infinite(elabels) & elabels < as.Date("1970-01-01")
plus_inf <- is.infinite(elabels) & elabels > as.Date("1970-01-01")
elabels_chr[minus_inf] <- symbol_infinity(minus = TRUE)
elabels_chr[plus_inf] <- symbol_infinity()
elabels_chr
}
#' @export
endpoint_labels.POSIXt <- function (breaks, raw, fmt = NULL, ...) {
elabels <- scaled_endpoints(breaks, raw = raw)
# same comment as endpoint_labels.Date above:
if (! inherits(elabels, "POSIXt")) return(NextMethod())
# set default format
fmt <- fmt %||% "%F %H:%M:%S"
elabels_chr <- apply_format(fmt, elabels)
minus_inf <- is.infinite(elabels) & elabels < as.POSIXct("1970-01-01")
plus_inf <- is.infinite(elabels) & elabels > as.POSIXct("1970-01-01")
elabels_chr[minus_inf] <- symbol_infinity(minus = TRUE)
elabels_chr[plus_inf] <- symbol_infinity()
elabels_chr
}
#' @export
endpoint_labels.quantileBreaks <- function (breaks, raw, fmt = NULL, ...) {
if (raw) return(NextMethod())
# set default format
fmt <- fmt %||% percent
elabels <- scaled_endpoints(breaks, raw = FALSE)
elabels <- apply_format(fmt, elabels)
return(elabels)
}
#' @export
endpoint_labels.sdBreaks <- function (breaks, raw, fmt = NULL, ...) {
if (raw) return(NextMethod())
# set default format
fmt <- fmt %||% "%.3g sd"
elabels <- scaled_endpoints(breaks, raw = FALSE)
elabels <- apply_format(fmt, elabels)
return(elabels)
}
#' Return numeric (or whatever) endpoints of breaks, possibly scaled
#'
#' @param breaks Breaks or numeric object
#' @param raw Logical. If `FALSE`, return endpoints scaled as e.g. sds or
#' quantiles
#'
#' @return Numbers, dates, etc. with no `breaks` class.
#' @noRd
scaled_endpoints <- function (breaks, raw) {
UseMethod("scaled_endpoints")
}
#' @export
scaled_endpoints.breaks <- function (breaks, raw) {
if (raw) {
unclass_breaks(breaks)
} else {
attr(breaks, "scaled_endpoints") %||% unclass_breaks(breaks)
}
}
#' @export
scaled_endpoints.default <- function (breaks, raw) {
if (raw) {
breaks
} else {
attr(breaks, "scaled_endpoints") %||% breaks
}
}
#' Apply `fmt` to an object
#'
#' @param fmt A one-argument function, or a character string.
#' @param endpoint Endpoints of a break. Various classes.
#'
#' @return A character vector.
#' @noRd
apply_format <- function (fmt, endpoint, ...) {
UseMethod("apply_format")
}
#' @export
apply_format.function <- function (fmt, endpoint, ...) {
fmt(endpoint, ...)
}
#' @export
#' @method apply_format character
apply_format.character <- function (fmt, endpoint, ...) {
UseMethod("apply_format.character", endpoint)
}
#' @export
#' @method apply_format.character default
apply_format.character.default <- function (fmt, endpoint, ...) {
base::format(endpoint, fmt, ...)
}
#' @export
#' @method apply_format.character numeric
apply_format.character.numeric <- function (fmt, endpoint, ...) {
sprintf(fmt, endpoint, ...)
}
#' @export
#' @method apply_format.character character
apply_format.character.character <- function (fmt, endpoint, ...) {
sprintf(fmt, endpoint, ...)
}
#' @export
#' @method apply_format list
apply_format.list <- function (fmt, endpoint, ...) {
UseMethod("apply_format.list", endpoint)
}
#' @export
#' @method apply_format.list default
apply_format.list.default <- function (fmt, endpoint, ...) {
do.call(base::format, c(list(x = endpoint), fmt))
}
is_format <- function (fmt) is.string(fmt) || is.function(fmt) || is.list(fmt)
on_failure(is_format) <- function(call, env) {
paste0(deparse(call$fmt), " is not a valid format (a string, list or function)")
}
#' Build interval endpoints for discrete-style labels
#'
#' Shared implementation for labelers that make open intervals non-overlapping
#' by shifting open endpoints inward by `unit`.
#'
#' @param breaks A breaks object.
#' @param unit Optional scalar unit. If `NULL`, no endpoint adjustment is made.
#' @param endpoints Optional endpoints vector. Defaults to `unclass_breaks(breaks)`.
#' @param singleton_mask Logical mask of singleton intervals.
#'
#' @return A list with `l`, `r`, `singletons`, `too_small`, `l_closed`,
#' and `r_closed`.
#' @noRd
discrete_interval_endpoints <- function(
breaks,
unit = NULL,
endpoints = NULL,
singleton_mask = singletons(breaks)
) {
assert_that(is.breaks(breaks))
len_breaks <- length(breaks)
endpoints <- endpoints %||% unclass_breaks(breaks)
left <- attr(breaks, "left")
l <- endpoints[-len_breaks]
r <- endpoints[-1]
left_l <- left[-len_breaks]
left_r <- left[-1]
if (!is.null(unit)) {
l[!left_l] <- l[!left_l] + unit
r[left_r] <- r[left_r] - unit
singleton_mask <- singleton_mask | (r == l)
too_small <- r < l
} else {
too_small <- rep(FALSE, len_breaks - 1L)
}
list(
l = l,
r = r,
singletons = singleton_mask,
too_small = too_small,
l_closed = left_l,
r_closed = !left_r
)
}
#' Truncates `num` to look nice, while preserving uniqueness
#'
#' @param num A numeric vector.
#'
#' @return A character vector
#' @noRd
unique_truncation <- function (num) {
want_unique <- ! duplicated(num) # "real" duplicates are allowed!
# we keep the first of each duplicate set.
for (digits in seq(4L, 22L)) {
res <- formatC(num, digits = digits, width = -1L)
if (anyDuplicated(res[want_unique]) == 0L) return(res)
}
stop("Could not format breaks to avoid duplicates")
}
em_dash <- function () {
if (l10n_info()[["UTF-8"]]) "\u2014" else "-"
}
symbol_infinity <- function (minus = FALSE) {
infty <- if (l10n_info()[["UTF-8"]]) "\u221e" else "Inf"
infty <- getOption("santoku.infinity", infty)
if (minus) paste0("-", infty) else infty
}
================================================
FILE: R/labels-single.R
================================================
#' Label chopped intervals by their midpoints
#'
#' This uses the midpoint of each interval for
#' its label.
#'
#' @inherit label-doc
#' @inherit first-last-doc
#'
#' @family labelling functions
#'
#' @export
#'
#' @examples
#' chop(1:10, c(2, 5, 8), lbl_midpoints())
lbl_midpoints <- function (
fmt = NULL,
single = NULL,
first = NULL,
last = NULL,
raw = deprecated()
) {
if (lifecycle::is_present(raw)) {
lifecycle::deprecate_stop("0.9.0", "lbl_midpoints(raw)", "chop(raw)")
}
function (breaks, raw = FALSE) {
assert_that(is.breaks(breaks))
break_nums <- scaled_endpoints(breaks, raw = raw)
l_nums <- break_nums[-length(break_nums)]
r_nums <- break_nums[-1]
# doing this, rather than (l_nums + r_nums)/2, works for e.g. Date objects:
midpoints <- l_nums + (r_nums - l_nums)/2
# we've applied raw already (anyway, midpoints is just a numeric)
midpoints <- endpoint_labels(midpoints, raw = TRUE, fmt = fmt)
gluer <- lbl_glue(label = "{m}", fmt = fmt, single = single, first = first,
last = last, m = midpoints)
labels <- gluer(breaks, raw = raw)
labels
}
}
#' Label chopped intervals by their left or right endpoints
#'
#' This is useful when the left endpoint unambiguously indicates the
#' interval. In other cases it may give errors due to duplicate labels.
#'
#' `lbl_endpoint()` is `r lifecycle::badge("defunct")` and gives an
#' error since santoku 1.0.0.
#'
#' @inherit label-doc
#' @inherit first-last-doc
#' @param left Flag. Use left endpoint or right endpoint?
#'
#' @family labelling functions
#'
#' @export
#'
#' @examples
#' chop(1:10, c(2, 5, 8), lbl_endpoints(left = TRUE))
#' chop(1:10, c(2, 5, 8), lbl_endpoints(left = FALSE))
#' if (requireNamespace("lubridate")) {
#' tab_width(
#' as.Date("2000-01-01") + 0:365,
#' months(1),
#' labels = lbl_endpoints(fmt = "%b")
#' )
#' }
#'
#' \dontrun{
#' # This gives breaks `[1, 2) [2, 3) {3}` which lead to
#' # duplicate labels `"2", "3", "3"`:
#' chop(1:3, 1:3, lbl_endpoints(left = FALSE))
#' }
lbl_endpoints <- function (
left = TRUE,
fmt = NULL,
single = NULL,
first = NULL,
last = NULL,
raw = deprecated()
) {
assert_that(is.flag(left))
if (lifecycle::is_present(raw)) {
lifecycle::deprecate_stop("0.9.0", "lbl_endpoints(raw)", "chop(raw)")
}
label <- if (left) "{l}" else "{r}"
lbl_glue(label, fmt = fmt, single = single, first = first, last = last)
}
#' @rdname lbl_endpoints
#' @export
lbl_endpoint <- function (
fmt = NULL,
raw = FALSE,
left = TRUE
) {
lifecycle::deprecate_stop(when = "0.8.0", what = "lbl_endpoint()",
with = "lbl_endpoints()")
}
#' Label chopped intervals in sequence
#'
#' `lbl_seq()` labels intervals sequentially, using numbers or letters.
#'
#' @param start String. A template for the sequence. See below.
#'
#' @details
#'`start` shows the first element of the sequence. It must contain exactly *one*
#' character out of the set "a", "A", "i", "I" or "1". For later elements:
#'
#' * "a" will be replaced by "a", "b", "c", ...
#' * "A" will be replaced by "A", "B", "C", ...
#' * "i" will be replaced by lower-case Roman numerals "i", "ii", "iii", ...
#' * "I" will be replaced by upper-case Roman numerals "I", "II", "III", ...
#' * "1" will be replaced by numbers "1", "2", "3", ...
#'
#' Other characters will be retained as-is.
#'
#' @family labelling functions
#' @inherit label-doc return
#'
#' @export
#'
#' @examples
#' chop(1:10, c(2, 5, 8), lbl_seq())
#'
#' chop(1:10, c(2, 5, 8), lbl_seq("i."))
#'
#' chop(1:10, c(2, 5, 8), lbl_seq("(A)"))
lbl_seq <- function(start = "a") {
assert_that(is.string(start))
# check like contains just one of a, A, i, I, 1
match <- gregexpr("(a|A|i|I|1)", start)[[1]]
if (length(match) > 1) stop("More than one a/A/i/I/1 found in `start`: ", start)
if (match == -1) stop("No a/A/i/I/1 found in `start`: ", start)
# replace that with the format-string and call lbl_manual appropriately
key <- substr(start, match, match)
fmt <- sub("(a|A|i|I|1)", "%s", start)
res <- switch(key,
"a" = function (breaks, raw = NULL) {
if (length(breaks) > 27L) {
stop("Can't use more than 26 intervals with lbl_seq(\"a\")")
}
sprintf(fmt, letters[seq_len(length(breaks) - 1L)])
},
"A" = function (breaks, raw = NULL) {
if (length(breaks) > 27L) {
stop("Can't use more than 26 intervals with lbl_seq(\"A\")")
}
sprintf(fmt, LETTERS[seq_len(length(breaks) - 1L)])
},
"i" = function (breaks, raw = NULL) {
sprintf(fmt, tolower(utils::as.roman(seq_len(length(breaks) - 1L))))
},
"I" = function (breaks, raw = NULL) {
sprintf(fmt, utils::as.roman(seq_len(length(breaks) - 1L)))
},
"1" = function (breaks, raw = NULL) {
sprintf(fmt, seq_len(length(breaks) - 1L))
}
)
return(res)
}
#' Defunct: label chopped intervals in a user-defined sequence
#'
#' `r lifecycle::badge("defunct")`
#'
#' `lbl_manual()` is defunct since santoku 1.0.0. It is little used and is not
#' closely related to the rest of the package. It also risks mislabelling
#' intervals, e.g. if intervals are extended. Use of `lbl_manual()` will give
#' an error.
#'
#' @param sequence A character vector of labels.
#' @inherit label-doc
#'
#' @family labelling functions
#'
#' @export
#'
#' @keywords internal
#'
#' @examples
#' \dontrun{
#' chop(1:10, c(2, 5, 8), lbl_manual(c("w", "x", "y", "z")))
#' # ->
#' chop(1:10, c(2, 5, 8), labels = c("w", "x", "y", "z"))
#' }
lbl_manual <- function (sequence, fmt = "%s") {
lifecycle::deprecate_stop("0.9.0", "lbl_manual()",
details = "Just specify `labels = sequence` instead.")
}
================================================
FILE: R/labels.R
================================================
#' @name label-doc
#' @param fmt String, list or function. A format for break endpoints.
#' @param raw `r lifecycle::badge("deprecated")`. Throws an error.
#' Use the `raw` argument to [chop()] instead.
#' @param symbol String: symbol to use for the dash.
#' @param ... Arguments passed to format methods.
#'
#' @section Formatting endpoints:
#'
#' If `fmt` is not `NULL` then it is used to format the endpoints.
#'
#' * If `fmt` is a string, then numeric endpoints will be formatted by
#' `sprintf(fmt, breaks)`; other endpoints, e.g. [Date][base::Dates] objects,
#' will be formatted by `format(breaks, fmt)`.
#'
#' * If `fmt` is a list, then it will be used as arguments to [format].
#'
#' * If `fmt` is a function, it should take a vector of numbers (or other objects
#' that can be used as breaks) and return a character vector. It may be helpful
#' to use functions from the `{scales}` package, e.g. [scales::label_comma()].
#'
#' @return A function that creates a vector of labels.
NULL
#' @name first-last-doc
#' @param single Glue string: label for singleton intervals. See [lbl_glue()]
#' for details. If `NULL`, singleton intervals will be labelled the same way
#' as other intervals.
#' @param first Glue string: override label for the first category. Write e.g.
#' `first = "<{r}"` to create a label like `"<18"`. See [lbl_glue()]
#' for details.
#' @param last String: override label for the last category. Write e.g.
#' `last = ">{l}"` to create a label like `">65"`. See [lbl_glue()]
#' for details.
NULL
#' Label chopped intervals using set notation
#'
#' These labels are the most exact, since they show you whether
#' intervals are "closed" or "open", i.e. whether they include their endpoints.
#'
#' Mathematical set notation looks like this:
#'
#' * \code{[a, b]}: all numbers `x` where `a <= x <= b`;
#' * \code{(a, b)}: all numbers where `a < x < b`;
#' * \code{[a, b)}: all numbers where `a <= x < b`;
#' * \code{(a, b]}: all numbers where `a < x <= b`;
#' * \code{{a}}: just the number `a` exactly.
#'
#' @inherit label-doc
#' @inherit first-last-doc
#'
#' @family labelling functions
#'
#' @export
#'
#' @examples
#'
#' tab(-10:10, c(-3, 0, 0, 3),
#' labels = lbl_intervals())
#'
#' tab(-10:10, c(-3, 0, 0, 3),
#' labels = lbl_intervals(fmt = list(nsmall = 1)))
#'
#' tab_evenly(runif(20), 10,
#' labels = lbl_intervals(fmt = percent))
#'
lbl_intervals <- function (
fmt = NULL,
single = "{{{l}}}",
first = NULL,
last = NULL,
raw = deprecated()
) {
if (lifecycle::is_present(raw)) {
lifecycle::deprecate_stop("0.9.0", "lbl_intervals(raw)", "chop(raw)")
}
interval_glue <- "{ifelse(l_closed, '[', '(')}{l}, {r}{ifelse(r_closed, ']', ')')}"
lbl_glue(label = interval_glue, single = single, fmt = fmt, first = first,
last = last)
}
#' Label discrete data
#'
#' `lbl_discrete()` creates labels for discrete data, such as integers.
#' For example, breaks
#' `c(1, 3, 4, 6, 7)` are labelled: `"1-2", "3", "4-5", "6-7"`.
#'
#' @inherit label-doc
#' @param unit Minimum difference between distinct values of data.
#' For integers, 1.
#' @inherit first-last-doc
#'
#' @details
#' No check is done that the data are discrete-valued. If they are not, then
#' these labels may be misleading. Here, discrete-valued means that if
#' `x < y`, then `x <= y - unit`.
#'
#' Be aware that Date objects may have non-integer values. See
#' [Date][base::Dates].
#'
#' @family labelling functions
#'
#' @export
#'
#' @examples
#' tab(1:7, c(1, 3, 5), lbl_discrete())
#'
#' tab(1:7, c(3, 5), lbl_discrete(first = "<= {r}"))
#'
#' tab(1:7 * 1000, c(1, 3, 5) * 1000, lbl_discrete(unit = 1000))
#'
#' # Misleading labels for non-integer data
#' chop(2.5, c(1, 3, 5), lbl_discrete())
#'
lbl_discrete <- function (
symbol = em_dash(),
unit = 1L,
fmt = NULL,
single = NULL,
first = NULL,
last = NULL
) {
assert_that(
is.string(symbol),
is.scalar(unit),
is.null(fmt) || is_format(fmt),
is.string(single) || is.null(single),
is.string(first) || is.null(first),
is.string(last) || is.null(last)
)
function (breaks, raw = NULL) {
assert_that(all(ceiling(as.numeric(breaks)) == floor(as.numeric(breaks))),
msg = "Non-integer breaks")
len_breaks <- length(breaks)
pieces <- discrete_interval_endpoints(
breaks = breaks,
unit = unit,
endpoints = unclass_breaks(breaks)
)
l <- pieces$l
r <- pieces$r
singletons <- pieces$singletons
too_small <- pieces$too_small
l_closed <- pieces$l_closed
r_closed <- pieces$r_closed
if (any(too_small)) {
warning("Intervals smaller than `unit` are labelled as \"--\"")
}
labels_l <- endpoint_labels(l, raw = FALSE, fmt = fmt)
labels_r <- endpoint_labels(r, raw = FALSE, fmt = fmt)
labels <- paste0(labels_l, symbol, labels_r)
labels[singletons] <- labels_l[singletons]
labels[too_small] <- "--"
if (! is.null(single)) {
labels[singletons] <- glue::glue(single, l = labels_l[singletons],
r = labels_r[singletons],
l_closed = l_closed[singletons],
r_closed = r_closed[singletons])
}
if (! is.null(first)) {
labels[1] <- glue::glue(first, l = labels_l[1], r = labels_r[1],
l_closed = l_closed[1], r_closed = r_closed[1])
}
if (! is.null(last)) {
ll <- len_breaks - 1
labels[ll] <- glue::glue(last, l = labels_l[ll], r = labels_r[ll],
l_closed = l_closed[ll], r_closed = r_closed[ll])
}
return(labels)
}
}
#' Label chopped intervals like 1-4, 4-5, ...
#'
#' This label style is user-friendly, but doesn't distinguish between
#' left- and right-closed intervals. It's good for continuous data
#' where you don't expect points to be exactly on the breaks.
#'
#' If you don't want unicode output, use `lbl_dash("-")`.
#'
#' @inherit label-doc
#' @inherit first-last-doc
#'
#' @family labelling functions
#'
#' @export
#'
#' @examples
#' chop(1:10, c(2, 5, 8), lbl_dash())
#'
#' chop(1:10, c(2, 5, 8), lbl_dash(" to ", fmt = "%.1f"))
#'
#' chop(1:10, c(2, 5, 8), lbl_dash(first = "<{r}"))
#'
#' pretty <- function (x) prettyNum(x, big.mark = ",", digits = 1)
#' chop(runif(10) * 10000, c(3000, 7000), lbl_dash(" to ", fmt = pretty))
lbl_dash <- function (
symbol = em_dash(),
fmt = NULL,
single = "{l}",
first = NULL,
last = NULL,
raw = deprecated()
) {
if (lifecycle::is_present(raw)) {
lifecycle::deprecate_stop("0.9.0", "lbl_dash(raw)", "chop(raw)")
}
label_glue <- paste0("{l}", symbol, "{r}")
lbl_glue(label = label_glue, fmt = fmt, single = single, first = first,
last = last, raw = raw)
}
================================================
FILE: R/non-standard-types-doc.R
================================================
#' Tips for chopping non-standard types
#'
#' Santoku can handle many non-standard types.
#'
#' * If objects can be compared using `<`, `==` etc. then they should
#' be choppable.
#' * Objects which can't be converted to numeric are handled within R code,
#' which may be slower.
#' * Character `x` and `breaks` are chopped with a warning.
#' * If `x` and `breaks` are not the same type, they should be able to
#' be cast to the same type, usually using [vctrs::vec_cast_common()].
#' * Not all chopping operations make sense, for example, [chop_mean_sd()]
#' on a character vector.
#' * For indexed objects such as [stats::ts()] objects, indices will be dropped
#' from the result.
#' * If you get errors, try setting `extend = FALSE` (but also file a bug report).
#' * To request support for a type, open an issue on Github.
#'
#' @name non-standard-types
#' @seealso brk-width-for-Datetime
NULL
================================================
FILE: R/santoku-cast.R
================================================
#' Hacked version of [vctrs::vec_cast_common()]
#'
#' @param x,y Vectors to cast
#'
#' @return A list of two vectors of the same class; or
#' errors, if that isn't possible.
#'
#' This almost always defers to `vctrs::vec_cast_common()`.
#'
#' Often, we are more relaxed than `vctrs` because
#' we have a more specific use case (comparing numeric
#' values). So e.g. we're fine with comparing a `ts`
#' object to a number, whereas other binary
#' operations might not make sense.
#'
#' @noRd
#'
santoku_cast_common <- function (x, y) {
UseMethod("santoku_cast_common")
}
#' Internal functions
#'
#' @name santoku-cast
#' @param x,y Vectors to cast.
#'
#' @return A list.
#' @keywords internal
#'
#' These are internal functions. Do not use.
NULL
# The rawNamespace below means NAMESPACE gets both the S3method() and the
# export() tag.
#' @export
#' @rdname santoku-cast
#' @method santoku_cast_common default
#' @rawNamespace export(santoku_cast_common.default)
santoku_cast_common.default <- function (x, y) {
UseMethod("santoku_cast_common.default", object = y)
}
#' @export
santoku_cast_common.default.default <- function (x, y) {
vctrs::vec_cast_common(x, y)
}
# Specific default.x methods are in their sections below.
# ==== double ====
# We have specific double methods just to catch bit64 objects
#' @export
#' @rdname santoku-cast
#' @method santoku_cast_common double
#' @rawNamespace export(santoku_cast_common.double)
santoku_cast_common.double <- function (x, y) {
UseMethod("santoku_cast_common.double", object = y)
}
#' @export
santoku_cast_common.double.default <- function (x, y) {
# almost always delegate to default
santoku_cast_common.default(x, y)
}
#' @export
santoku_cast_common.double.integer64 <- function (x, y) {
loadNamespace("bit64")
# we cast the integer64 to double.
# See santoku_cast_common.integer64.double below for why.
list(x, as.double(y))
}
# ==== Date ====
# We delegate to vctrs for Date+numeric (which gives an error)
# Not obvious how to interpret conversion of Date to numeric
#' @export
#' @rdname santoku-cast
#' @method santoku_cast_common Date
#' @rawNamespace export(santoku_cast_common.Date)
santoku_cast_common.Date <- function (x, y) {
UseMethod("santoku_cast_common.Date", object = y)
}
#' @export
santoku_cast_common.Date.Date <- function (x, y) {
list(x, y)
}
#' @export
santoku_cast_common.Date.POSIXct <- function (x, y) {
list(as.POSIXct(x), y)
}
# ==== POSIXct ====
# We delegate to vctrs for POSIXct/numeric, see Date above
#' @export
#' @rdname santoku-cast
#' @method santoku_cast_common POSIXct
#' @rawNamespace export(santoku_cast_common.POSIXct)
santoku_cast_common.POSIXct <- function (x, y) {
UseMethod("santoku_cast_common.POSIXct", object = y)
}
#' @export
santoku_cast_common.POSIXct.POSIXct <- function (x, y) {
list(x, y)
}
#' @export
santoku_cast_common.POSIXct.Date <- function (x, y) {
list(x, as.POSIXct(y))
}
# ==== ts ====
#' @export
#' @rdname santoku-cast
#' @method santoku_cast_common ts
#' @rawNamespace export(santoku_cast_common.ts)
santoku_cast_common.ts <- function (x, y) {
UseMethod("santoku_cast_common.ts", object = y)
}
#' @export
santoku_cast_common.ts.default <- function (x, y) {
# We recall so that we can pick up anything
# unusual in y
santoku_cast_common(unclass(x), y)
}
#' @export
santoku_cast_common.default.ts <- function (x, y) {
santoku_cast_common(x, unclass(y))
}
# ==== zoo ====
#' @export
#' @rdname santoku-cast
#' @method santoku_cast_common zoo
#' @rawNamespace export(santoku_cast_common.zoo)
santoku_cast_common.zoo <- function (x, y) {
UseMethod("santoku_cast_common.zoo", object = y)
}
# we don't have a zoo.zoo method because returning
# two zoo objects would cause comparisons to only
# work where the indices are the same. So, we always
# work on the underlying data.
#' @export
santoku_cast_common.zoo.default <- function (x, y) {
loadNamespace("zoo")
santoku_cast_common(zoo::coredata(x), y)
}
#' @export
santoku_cast_common.default.zoo <- function (x, y) {
loadNamespace("zoo")
santoku_cast_common(x, zoo::coredata(y))
}
# ==== integer64 ====
#' @export
#' @rdname santoku-cast
#' @method santoku_cast_common integer64
#' @rawNamespace export(santoku_cast_common.integer64)
santoku_cast_common.integer64 <- function (x, y) {
UseMethod("santoku_cast_common.integer64", object = y)
}
#' @export
santoku_cast_common.integer64.integer64 <- function (x, y) {
list(x, y)
}
#' @export
santoku_cast_common.integer64.double <- function (x, y) {
loadNamespace("bit64")
# we cast the integer64 to double.
# This may lose precision, but if so, it gives a warning.
# If we cast double to integer64, then
# e.g. chop(as.integer64(1:5), 2.5)
# silently converts 2.5 to 2 and gives a wrong answer
list(as.double(x), y)
}
#' @export
santoku_cast_common.integer64.default <- function (x, y) {
loadNamespace("bit64")
santoku_cast_common(x, bit64::as.integer64(y))
}
#' @export
santoku_cast_common.default.integer64 <- function (x, y) {
loadNamespace("bit64")
santoku_cast_common(bit64::as.integer64(x), y)
}
# ==== hexmode ====
#' @export
#' @rdname santoku-cast
#' @method santoku_cast_common hexmode
#' @rawNamespace export(santoku_cast_common.hexmode)
santoku_cast_common.hexmode <- function (x, y) {
UseMethod("santoku_cast_common.hexmode", object = y)
}
#' @export
santoku_cast_common.hexmode.hexmode <- function (x, y) {
# if both items are hexmode, OK fine.
list(x, y)
}
#' @export
santoku_cast_common.hexmode.default <- function (x, y) {
santoku_cast_common(as.numeric(x), y)
}
#' @export
santoku_cast_common.default.hexmode <- function (x, y) {
santoku_cast_common(x, as.numeric(y))
}
# ==== octmode ====
#' @export
#' @rdname santoku-cast
#' @method santoku_cast_common octmode
#' @rawNamespace export(santoku_cast_common.octmode)
santoku_cast_common.octmode <- function (x, y) {
UseMethod("santoku_cast_common.octmode", object = y)
}
#' @export
santoku_cast_common.octmode.octmode <- function (x, y) {
list(x, y)
}
#' @export
santoku_cast_common.octmode.default <- function (x, y) {
santoku_cast_common(as.numeric(x), y)
}
#' @export
santoku_cast_common.default.octmode <- function (x, y) {
santoku_cast_common(x, as.numeric(y))
}
================================================
FILE: R/santoku-package.R
================================================
#' @import assertthat
NULL
#' A versatile cutting tool for R: package overview and options
#'
#' santoku is a tool for cutting data into intervals. It provides
#' the function [chop()], which is similar to base R's [cut()] or `Hmisc::cut2()`.
#' `chop(x, breaks)` takes a vector `x` and returns a factor of the
#' same length, coding which interval each element of `x` falls into.
#'
#' Here are some advantages of santoku:
#'
#' * By default, `chop()` always covers the whole range of the data, so you
#' won't get unexpected `NA` values.
#'
#' * Unlike `cut()` or `cut2()`, `chop()` can handle single values as well as
#' intervals. For example, `chop(x, breaks = c(1, 2, 2, 3))` will create a
#' separate factor level for values exactly equal to 2.
#'
#' * Flexible and easy labelling.
#'
#' * Convenience functions for creating quantile intervals, evenly-spaced
#' intervals or equal-sized groups.
#'
#' * Convenience functions to quickly tabulate chopped data.
#'
#' * Can chop numbers, dates, date-times and other objects.
#'
#' These advantages make santoku especially useful for exploratory analysis,
#' where you may not know the range of your data in advance.
#'
#' To get started, read the vignette:
#'
#' ```r
#' vignette("santoku")
#' ```
#'
#' For more details, start with the documentation for [chop()].
#'
#' # Options
#'
#' Santoku has two options:
#'
#' * `options("santoku.infinity")` sets the symbol for infinity in breaks. The default is
#' `NULL`, in which case the infinity symbol is used on platforms that support it, otherwise
#' `"Inf"` is used.
#'
#' * `options("santoku.warn_character")` warns if you try to chop a character vector. Set to
#' `FALSE` to turn off this warning.
"_PACKAGE"
# The following block is used by usethis to automatically manage
# roxygen namespace tags. Modify with care!
## usethis namespace: start
#' @importFrom Rcpp sourceCpp
#' @useDynLib santoku, .registration = TRUE
## usethis namespace: end
NULL
================================================
FILE: R/tab.R
================================================
#' @rdname chop
#' @export
#' @examples
#' tab(1:10, c(2, 5, 8))
#'
tab <- function (
x,
breaks,
labels = lbl_intervals(),
extend = NULL,
left = TRUE,
close_end = TRUE,
raw = NULL,
drop = TRUE
) {
default_table(
chop(
x = x,
breaks = breaks,
labels = labels,
extend = extend,
left = left,
close_end = close_end,
raw = raw,
drop = drop
)
)
}
#' @rdname chop_width
#' @export
#' @order 3
#' @examples
#' tab_width(1:10, 2, start = 0)
#'
tab_width <- function (
x,
width,
start,
...,
left = sign(width) > 0
) {
default_table(
chop_width(x = x, width = width, start = start, ..., left = left)
)
}
#' @rdname chop_evenly
#' @export
#' @order 3
tab_evenly <- function (
x,
intervals,
...
) {
default_table(
chop_evenly(x = x, intervals = intervals, ...)
)
}
#' @rdname chop_proportions
#' @export
#' @order 3
tab_proportions <- function (
x,
proportions,
...,
raw = TRUE
) {
default_table(
chop_proportions(x = x, proportions = proportions, ..., raw = raw)
)
}
#' @rdname chop_n
#' @export
#' @order 3
#' @examples
#' tab_n(1:10, 5)
#'
#' # fewer elements in one group
#' tab_n(1:10, 4)
#'
tab_n <- function (
x,
n,
...,
tail = "split"
) {
default_table(chop_n(x = x, n = n, ..., tail = tail))
}
#' @rdname chop_mean_sd
#' @export
#' @order 3
#' @examples
#' tab_mean_sd(1:10)
#'
tab_mean_sd <- function (
x,
sds = 1:3,
...,
raw = FALSE
) {
default_table(chop_mean_sd(x = x, sds = sds, ..., raw = raw))
}
#' @rdname chop_pretty
#' @export
#' @order 3
#' @examples
#' tab_pretty(1:10)
#'
tab_pretty <- function (x, n = 5, ...) {
default_table(chop_pretty(x = x, n = n, ...))
}
#' @rdname chop_quantiles
#' @export
#' @order 3
#' @examples
#' set.seed(42)
#' tab_quantiles(rnorm(100), probs = 1:3/4, raw = TRUE)
#'
tab_quantiles <- function (
x,
probs,
...,
left = is.numeric(x),
raw = FALSE
) {
default_table(
chop_quantiles(x = x, probs = probs, ..., left = left, raw = raw)
)
}
#' @rdname chop_quantiles
#' @export
#' @order 3
tab_deciles <- function (x, ...) {
default_table(chop_deciles(x = x, ...))
}
#' @rdname chop_equally
#' @export
#' @order 3
tab_equally <- function (
x,
groups,
...,
left = is.numeric(x),
raw = TRUE
) {
default_table(
chop_equally(x = x, groups = groups, ..., left = left, raw = raw)
)
}
#' @rdname chop_fn
#' @export
#' @order 3
tab_fn <- function (
x,
fn,
...,
extend = NULL,
left = TRUE,
close_end = TRUE,
raw = NULL,
drop = TRUE
) {
default_table(
chop_fn(x = x, fn = fn, ..., extend = extend, left = left,
close_end = close_end, raw = raw, drop = drop)
)
}
#' @rdname chop_spikes
#' @export
#' @order 3
tab_spikes <- function (
x,
breaks,
...,
n = NULL,
prop = NULL
) {
default_table(
chop_spikes(x = x, breaks = breaks, ..., n = n, prop = prop)
)
}
#' @rdname dissect
#' @export
#' @order 2
tab_dissect <- function (
x,
breaks,
...,
n = NULL,
prop = NULL
) {
default_table(
dissect(x = x, breaks = breaks, ..., n = n, prop = prop)
)
}
default_table <- function (x) {
table(x, useNA = "ifany", dnn = NULL)
}
================================================
FILE: R/utils.R
================================================
#' Define singleton intervals explicitly
#'
#' `exactly()` duplicates its input.
#' It lets you define singleton intervals like this: `chop(x, c(1, exactly(2), 3))`.
#' This is the same as `chop(x, c(1, 2, 2, 3))` but conveys your intent more
#' clearly.
#'
#' @param x A numeric vector.
#'
#' @return The same as `rep(x, each = 2)`.
#' @export
#'
#' @examples
#' chop(1:10, c(2, exactly(5), 8))
#'
#' # same:
#' chop(1:10, c(2, 5, 5, 8))
exactly <- function (x) rep(x, each = 2)
#' Simple percentage formatter
#'
#' `percent()` formats `x` as a percentage.
#' For a wider range of formatters, consider the [`scales`
#' package](https://cran.r-project.org/package=scales).
#'
#' @param x Numeric values.
#'
#' @return `x` formatted as a percent.
#' @export
#'
#' @examples
#' percent(0.5)
percent <- function (x) {
paste0(unique_truncation(x * 100), "%")
}
#' Report singleton intervals
#'
#' @param breaks A breaks object
#'
#' @return
#' A logical vector of `length(breaks) - 1`, `TRUE`
#' if the corresponding interval is a singleton.
#' @noRd
#'
#' @examples
#' brk <- brk_res(brk_default(c(1, 1, 2, 3, 3, 4)))
#' brk
#' # Breaks object: {1} (1, 2) [2, 3) {3} (3, 4)
#'
#' singletons(brk)
#' # TRUE FALSE FALSE TRUE FALSE
singletons <- function (breaks) {
duplicated(breaks)[-1]
}
#' Find duplicates that would be illegal breaks
#'
#' @param x A vector, which should be sorted
#'
#' @return A logical vector of length(x), TRUE if the corresponding element
#' is the second duplicate in a row.
#' @noRd
#'
#' @examples
#' find_illegal_duplicates(c(1, 2, 2, 3, 3, 3, 4))
find_illegal_duplicates <- function (x) {
if (length(x) == 0) return(logical(0))
dupes <- duplicated(x)
# If element n is duplicated, and element n+1 is duplicated, then n + 1
# is illegal.
# The first element is never a duplicated middle.
c(FALSE, dupes[-length(dupes)] & dupes[-1])
}
`%||%` <- function (x, y) if (is.null(x)) y else x
quiet_min <- function (x) suppressWarnings(min(x, na.rm = TRUE))
quiet_max <- function (x) suppressWarnings(max(x, na.rm = TRUE))
#' Stricter `as.numeric`
#'
#' This converts warnings to errors, and errors if any NAs are introduced,
#' but is less strict than `vctrs::vec_cast()`
#'
#' @param x A vector
#'
#' @return `as.numeric(x)`, with no new NAs
#' @noRd
#'
strict_as_numeric <- function (x) {
nas <- is.na(x)
x <- tryCatch(as.numeric(x),
warning = function (w) stop("Warning from as.numeric(x)")
)
if (any(is.na(x) & ! nas)) stop("Could not convert some elements")
x
}
#' Test a break
#'
#' @param brk_fun A call to a `brk_` function
#' @param x,extend,left,close_end Passed in to `brk_fun`
#'
#' @return A `breaks` object.
#' @noRd
#'
#' @examples
#' brk_res(brk_default(1:3))
#'
brk_res <- function (
brk_fun,
x = 1:2,
extend = FALSE,
left = TRUE,
close_end = TRUE
) {
brk_fun(x, extend = extend, left = left, close_end = close_end)
}
================================================
FILE: README.Rmd
================================================
---
output: github_document
---
<!-- README.md is generated from README.Rmd. Please edit that file -->
```{r, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
fig.path = "man/figures/README-",
out.width = "100%"
)
```
# santoku <img src="man/figures/logo.png" align="right" alt="santoku logo" width="120" />
<!-- badges: start -->
[](https://CRAN.R-project.org/package=santoku)
[](https://lifecycle.r-lib.org/articles/stages.html#stable)
[](https://CRAN.R-project.org/package=santoku)
[](https://hughjonesd.r-universe.dev/santoku)
[](https://github.com/hughjonesd/santoku/actions)
[](https://app.codecov.io/gh/hughjonesd/santoku?branch=master)
<!-- badges: end -->
santoku is a versatile cutting tool for R. It provides `chop()`, a replacement
for `base::cut()`.
## Installation
Install from [r-universe](https://r-universe.dev):
``` r
install.packages("santoku", repos = c("https://hughjonesd.r-universe.dev",
"https://cloud.r-project.org"))
```
Or from CRAN:
``` r
install.packages("santoku")
```
Or get the development version from github:
``` r
# install.packages("remotes")
remotes::install_github("hughjonesd/santoku")
```
```{r, child = 'advantages.Rmd'}
```
## Examples
```{r}
library(santoku)
```
`chop` returns a factor:
```{r}
chop(1:5, c(2, 4))
```
Include a number twice to match it exactly:
```{r}
chop(1:5, c(2, 2, 4))
```
Use names in breaks for labels:
```{r}
chop(1:5, c(Low = 1, Mid = 2, High = 4))
```
Or use `lbl_*` functions:
```{r}
chop(1:5, c(2, 4), labels = lbl_dash())
```
Chop into fixed-width intervals:
```{r}
chop_width(runif(10), 0.1)
```
Or into fixed-size groups:
```{r}
chop_n(1:10, 5)
```
Chop dates by calendar month, then tabulate:
```{r}
library(lubridate)
dates <- as.Date("2021-12-31") + 1:90
tab_width(dates, months(1), labels = lbl_discrete(fmt = "%d %b"))
```
For more information, see the [vignette](https://hughjonesd.github.io/santoku/articles/santoku.html).
================================================
FILE: README.md
================================================
<!-- README.md is generated from README.Rmd. Please edit that file -->
# santoku <img src="man/figures/logo.png" align="right" alt="santoku logo" width="120" />
<!-- badges: start -->
[](https://CRAN.R-project.org/package=santoku)
[](https://lifecycle.r-lib.org/articles/stages.html#stable)
[](https://CRAN.R-project.org/package=santoku)
[](https://hughjonesd.r-universe.dev/santoku)
[](https://github.com/hughjonesd/santoku/actions)
[](https://app.codecov.io/gh/hughjonesd/santoku?branch=master)
<!-- badges: end -->
santoku is a versatile cutting tool for R. It provides `chop()`, a
replacement for `base::cut()`.
## Installation
Install from [r-universe](https://r-universe.dev):
``` r
install.packages("santoku", repos = c("https://hughjonesd.r-universe.dev",
"https://cloud.r-project.org"))
```
Or from CRAN:
``` r
install.packages("santoku")
```
Or get the development version from github:
``` r
# install.packages("remotes")
remotes::install_github("hughjonesd/santoku")
```
## Advantages
Here are some advantages of santoku:
- By default, `chop()` always covers the whole range of the data, so you
won’t get unexpected `NA` values.
- `chop()` can handle single values as well as intervals. For example,
`chop(x, breaks = c(1, 2, 2, 3))` will create a separate factor level
for values exactly equal to 2.
- `chop()` can handle many kinds of data, including numbers, dates and
times, and [units](https://r-quantities.github.io/units/).
- `chop_*` functions create intervals in many ways, using quantiles of
the data, standard deviations, fixed-width intervals, equal-sized
groups, or pretty intervals for use in graphs.
- It’s easy to label intervals: use names for your breaks vector, or use
a `lbl_*` function to create interval notation like `[1, 2)`, dash
notation like `1-2`, or arbitrary styles using `glue::glue()`.
- `tab_*` functions quickly chop data, then tabulate it.
These advantages make santoku especially useful for exploratory
analysis, where you may not know the range of your data in advance.
## Examples
``` r
library(santoku)
```
`chop` returns a factor:
``` r
chop(1:5, c(2, 4))
#> [1] [1, 2) [2, 4) [2, 4) [4, 5] [4, 5]
#> Levels: [1, 2) [2, 4) [4, 5]
```
Include a number twice to match it exactly:
``` r
chop(1:5, c(2, 2, 4))
#> [1] [1, 2) {2} (2, 4) [4, 5] [4, 5]
#> Levels: [1, 2) {2} (2, 4) [4, 5]
```
Use names in breaks for labels:
``` r
chop(1:5, c(Low = 1, Mid = 2, High = 4))
#> [1] Low Mid Mid High High
#> Levels: Low Mid High
```
Or use `lbl_*` functions:
``` r
chop(1:5, c(2, 4), labels = lbl_dash())
#> [1] 1—2 2—4 2—4 4—5 4—5
#> Levels: 1—2 2—4 4—5
```
Chop into fixed-width intervals:
``` r
chop_width(runif(10), 0.1)
#> [1] [0.368, 0.468) [0.268, 0.368) [0.768, 0.868] [0.568, 0.668)
#> [5] [0.668, 0.768) [0.768, 0.868] [0.06801, 0.168) [0.668, 0.768)
#> [9] [0.06801, 0.168) [0.468, 0.568)
#> 7 Levels: [0.06801, 0.168) [0.268, 0.368) [0.368, 0.468) ... [0.768, 0.868]
```
Or into fixed-size groups:
``` r
chop_n(1:10, 5)
#> [1] [1, 6) [1, 6) [1, 6) [1, 6) [1, 6) [6, 10] [6, 10] [6, 10] [6, 10]
#> [10] [6, 10]
#> Levels: [1, 6) [6, 10]
```
Chop dates by calendar month, then tabulate:
``` r
library(lubridate)
#>
#> Attaching package: 'lubridate'
#> The following objects are masked from 'package:base':
#>
#> date, intersect, setdiff, union
dates <- as.Date("2021-12-31") + 1:90
tab_width(dates, months(1), labels = lbl_discrete(fmt = "%d %b"))
#> 01 Jan—31 Jan 01 Feb—28 Feb 01 Mar—31 Mar
#> 31 28 31
```
For more information, see the
[vignette](https://hughjonesd.github.io/santoku/articles/santoku.html).
================================================
FILE: TODO.md
================================================
# TODO
* Work on tests
- tests for `left` and `close_end` arguments
- tests for `brk_default`
- `brk_width()` needs tests which match the guarantees in the documentation
- ditto for `brk_evenly()` which now uses its own implementation to
guarantee exactly `intervals` intervals
- systematic tests for `brk_*` functions
* Implement a simple `Infinity` class that automatically casts to any other
class and is always > or < than any other element? Then replace the `class_bounds()`
complexity?
- The problem at the moment is that `vec_cast()` is highly unreliable and
you never know if a particular class will accept `Inf`.
- An infinity class would be fine, but how does that go into the existing
`breaks` object which has its own underlying class?
- Might be more reasonable just not to add `Inf` or `-Inf` elements. Instead,
record whether the breaks have left and right "infinity" set. Then just
add numeric infinity to the breaks before you call `categorize_impl` (or
the R version). In particular, e.g. `integer64` doesn't like `Inf` or `-Inf`
but it does have very large numbers in `bit64::lim.integer64` which look
ugly and which only exist to be lower/higher than everything else anyway...
- But NB this requires a new way to create the labels, and that kinda
sucks....
# Thoughts on errors
* On the whole, we don't want to error out if `x` is weird. `x` is data. But
if e.g. `breaks` are weird, we can error out.
- Exception: `x` is the wrong class or type.
* In some cases we want to guarantee the set of breaks.
- e.g. `brk_manual()` with `extend` set.
* In other cases, e.g. `brk_evenly()` we don't need to make such a guarantee.
# Questions
* Is it really OK to have `left = FALSE` as the default in `chop_quantiles()`,
`chop_evenly()` and friends?
- the alternative is to do it only when `x` is non-numeric.
- that makes the surprise rarer, but rare surprises can be worse... and
it adds complexity since the functions have to be generic.
- another alternative: `chop` sets `left = FALSE` for non-numeric `x`. Probably
better.
* Do we need `drop`?
- should `drop` have a default of `! isTRUE(extend)` i.e. be `FALSE` when
`extend = TRUE`?
# Questions with a (provisional) answer
* Should we have a flag to return characters?
- No, we have `labels = NULL` for integer codes only though.
* Should we put a `percent` argument into `brk_quantiles()` so it can store
scaled endpoints as proportions rather than percentages (the current default)?
- My sense is, not unless someone asks.
- Oh, someone just did ask; more generally though.
* Should `close_end = TRUE` argument come before `...` in `chop_` variants?
- No. We don't want people to set it by position, so distinguish it from
the initial arguments.
* What to do about `tidyr::chop()`
- Current answer: fuck 'em. (NB: just kidding. I am a huge tidyverse fan.)
- We provide `kiru()`. So on the REPL, people can just use `kiru()` if they
load santoku first. If they load santoku second, they'll have to use
`tidyr::chop()`, but reading the documentation, I suspect this will be rare.
- For programming, people should probably used the fully qualified name
anyway.
* When to extend?
- I think default should be "if necessary" (`extend = NULL`); should always
extend to Inf, -Inf so that these break labels are not data-dependent
- Tension between wanting something predictable in your new data, vs. something
readable in `tab_*`. E.g.
```r
tab_size(1:9, 3, lbl_seq())
```
should surely return labels a, b, c. But this means we aren't always
extending.
* Should we allow vector `labels` to be longer than necessary?
+ lets people do e.g. `chop(rnorm(100), -2:2, LETTERS)`
- but might hide errors
- overall I'm against
* Is the label interface right? Problem exposed by `brk_mean_sd`: if
we aren't sure whether data gets extended, then how do we know what
the labels should be?
- maybe label functions should have access to `x`?
- or should they be informed if breaks got extended?
- or could the breaks object know how to extend its labels?
- current solution: labels get `extend`
- I think better: `breaks` objects include suggested labels which
the user can override. That way they always have the info necessary.
- We could also divide labelling into two parts:
1. choosing the break numbers (these may not be the actual values, e.g
they could be quantiles or std errs from 0)
2. formatting these numbers, and with dashes, set notation etc
- So maybe `brk_*` functions always return break numbers;
then labels decide how to format them?
* Should we automatically sort breaks, or throw an error if they're unsorted?
- or a warning?
- currently an error
* What if `breaks = c(1, 2, 2, 2, 3)`?
- throw an error
* For some cases e.g. `brk_quantiles`, `brk_width`, the data may not work
well e.g. if it is all NA. What is an empty set of breaks?
# Possible interfaces
- `hist_xxx` functions for histograms/barplots? (how to treat singletons?)
- `grp_xxx` for group_by? Hmmm...
- New label interface to replace `lbl_sequence`:
`lbl_style("1."), lbl_style("(i)"), lbl_style("A")` etc.?
- Still wonder, could we drop `extend` which adds complexity and just
have `only()` or `extend()` as new breaks functions?
# Other ideas
- Speedup categorize by only checking left intervals, add 1 if its past
each interval [NO: actually no fewer checks in the end...]
- Speedup by using pointers? hmm, magic...
================================================
FILE: _pkgdown.yml
================================================
destination: docs
url: https://hughjonesd.github.io/santoku
template:
bootstrap: 5
bootswatch: darkly
theme: arrow-dark
bslib:
link-color: "#f9ea6b"
link-decoration: none
navbar-dark-hover-color: "#f9ea6b"
navbar-dark-active-color: "#f9ea6b"
tutorials:
- name: 00-visualintroduction
title: A visual introduction to santoku
url: https://hughjonesd.github.io/visual-introduction.html
- name: 01-chopping-dates
title: Chopping dates with santoku
url: https://hughjonesd.github.io/chopping-dates-with-santoku.html
reference:
- title: Package overview
- contents:
- "`santoku-package`"
- title: Basic chop functions
desc: Cut a vector into intervals
- contents:
- chop
- kiru
- fillet
- tab
- title: Chopping by width
desc: Cut a vector into intervals defined by width
- contents:
- matches("width")
- ends_with("proportions")
- ends_with("evenly")
- title: Chopping by n
desc: Cut a vector into intervals defined by number of elements
- contents:
- ends_with("_n")
- ends_with("quantiles")
- ends_with("equally")
- title: Chopping and separating
desc: Cut a vector into intervals, separating out common values
- contents:
- ends_with("spikes")
- ends_with("dissect")
- title: Other chop functions
desc: Miscellaneous ways to cut a vector into intervals
- contents:
- ends_with("mean_sd")
- ends_with("pretty")
- ends_with("fn")
- brk_default
- brk_manual
- title: Label functions
desc: Specify how to label the chopped intervals
- contents:
- starts_with("lbl")
- title: Miscellaneous
desc: Other helper functions
- contents:
- -starts_with("chop|brk|lbl|santoku")
- -ends_with("dissect")
- -fillet
================================================
FILE: advantages.Rmd
================================================
## Advantages
Here are some advantages of santoku:
* By default, `chop()` always covers the whole range of the data, so you
won't get unexpected `NA` values.
* `chop()` can handle single values as well as intervals. For example,
`chop(x, breaks = c(1, 2, 2, 3))` will create a separate factor level for
values exactly equal to 2.
* `chop()` can handle many kinds of data, including numbers, dates and
times, and [units](https://r-quantities.github.io/units/).
* `chop_*` functions create intervals in many ways, using quantiles of the data,
standard deviations, fixed-width intervals, equal-sized groups, or
pretty intervals for use in graphs.
* It's easy to label intervals: use names for your breaks vector, or
use a `lbl_*` function to create interval notation like `[1, 2)`, dash
notation like `1-2`, or arbitrary styles using `glue::glue()`.
* `tab_*` functions quickly chop data, then tabulate it.
These advantages make santoku especially useful for exploratory analysis,
where you may not know the range of your data in advance.
================================================
FILE: codecov.yml
================================================
comment: false
coverage:
status:
project:
default:
target: auto
threshold: 1%
patch:
default:
target: auto
threshold: 1%
================================================
FILE: cran-comments.md
================================================
Version 1.2.1. Fixes (I hope) a testing bug exposed on CRAN.
## Test environments
* local (macOS), R 4.6.0
* github (windows, macOS, Ubuntu), devel and release
* win-builder (windows), devel and release
## R CMD check results
* Fine on all platforms.
## Reverse dependencies
* None.
================================================
FILE: docs/404.html
================================================
<!DOCTYPE html>
<!-- Generated by pkgdown: do not edit by hand --><html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Page not found (404) • santoku</title>
<!-- favicons --><link rel="icon" type="image/png" sizes="96x96" href="https://hughjonesd.github.io/santoku/favicon-96x96.png">
<link rel="icon" type="”image/svg+xml”" href="https://hughjonesd.github.io/santoku/favicon.svg">
<link rel="apple-touch-icon" sizes="180x180" href="https://hughjonesd.github.io/santoku/apple-touch-icon.png">
<link rel="icon" sizes="any" href="https://hughjonesd.github.io/santoku/favicon.ico">
<link rel="manifest" href="https://hughjonesd.github.io/santoku/site.webmanifest">
<script src="https://hughjonesd.github.io/santoku/deps/jquery-3.6.0/jquery-3.6.0.min.js"></script><meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link href="https://hughjonesd.github.io/santoku/deps/bootstrap-5.3.1/bootstrap.min.css" rel="stylesheet">
<script src="https://hughjonesd.github.io/santoku/deps/bootstrap-5.3.1/bootstrap.bundle.min.js"></script><link href="https://hughjonesd.github.io/santoku/deps/font-awesome-6.5.2/css/all.min.css" rel="stylesheet">
<link href="https://hughjonesd.github.io/santoku/deps/font-awesome-6.5.2/css/v4-shims.min.css" rel="stylesheet">
<script src="https://hughjonesd.github.io/santoku/deps/headroom-0.11.0/headroom.min.js"></script><script src="https://hughjonesd.github.io/santoku/deps/headroom-0.11.0/jQuery.headroom.min.js"></script><script src="https://hughjonesd.github.io/santoku/deps/bootstrap-toc-1.0.1/bootstrap-toc.min.js"></script><script src="https://hughjonesd.github.io/santoku/deps/clipboard.js-2.0.11/clipboard.min.js"></script><script src="https://hughjonesd.github.io/santoku/deps/search-1.0.0/autocomplete.jquery.min.js"></script><script src="https://hughjonesd.github.io/santoku/deps/search-1.0.0/fuse.min.js"></script><script src="https://hughjonesd.github.io/santoku/deps/search-1.0.0/mark.min.js"></script><!-- pkgdown --><script src="https://hughjonesd.github.io/santoku/pkgdown.js"></script><link href="https://hughjonesd.github.io/santoku/extra.css" rel="stylesheet">
<meta property="og:title" content="Page not found (404)">
<meta property="og:image" content="https://hughjonesd.github.io/santoku/logo.png">
</head>
<body>
<a href="https://hughjonesd.github.io/santoku/#main" class="visually-hidden-focusable">Skip to contents</a>
<nav class="navbar navbar-expand-lg fixed-top bg-primary" data-bs-theme="dark" aria-label="Site navigation"><div class="container">
<a class="navbar-brand me-2" href="https://hughjonesd.github.io/santoku/index.html">santoku</a>
<small class="nav-text text-muted me-auto" data-bs-toggle="tooltip" data-bs-placement="bottom" title="">1.2.0</small>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div id="navbar" class="collapse navbar-collapse ms-3">
<ul class="navbar-nav me-auto">
<li class="nav-item"><a class="nav-link" href="https://hughjonesd.github.io/santoku/articles/santoku.html">Get started</a></li>
<li class="nav-item"><a class="nav-link" href="https://hughjonesd.github.io/santoku/reference/index.html">Reference</a></li>
<li class="nav-item dropdown">
<button class="nav-link dropdown-toggle" type="button" id="dropdown-articles" data-bs-toggle="dropdown" aria-expanded="false" aria-haspopup="true">Articles</button>
<ul class="dropdown-menu" aria-labelledby="dropdown-articles">
<li><a class="dropdown-item" href="https://hughjonesd.github.io/santoku/articles/website-articles/performance.html">Performance</a></li>
<li><a class="dropdown-item" href="https://hughjonesd.github.io/santoku/articles/whats-new-in-0-9-0.html">What's new in santoku 0.9.0</a></li>
</ul>
</li>
<li class="nav-item dropdown">
<button class="nav-link dropdown-toggle" type="button" id="dropdown-tutorials" data-bs-toggle="dropdown" aria-expanded="false" aria-haspopup="true">Tutorials</button>
<ul class="dropdown-menu" aria-labelledby="dropdown-tutorials">
<li><a class="dropdown-item" href="https://hughjonesd.github.io/santoku/tutorials/00-visualintroduction.html">A visual introduction to santoku</a></li>
<li><a class="dropdown-item" href="https://hughjonesd.github.io/santoku/tutorials/01-chopping-dates.html">Chopping dates with santoku</a></li>
</ul>
</li>
<li class="nav-item"><a class="nav-link" href="https://hughjonesd.github.io/santoku/news/index.html">Changelog</a></li>
</ul>
<ul class="navbar-nav">
<li class="nav-item"><form class="form-inline" role="search">
<input class="form-control" type="search" name="search-input" id="search-input" autocomplete="off" aria-label="Search site" placeholder="Search for" data-search-index="search.json">
</form></li>
<li class="nav-item"><a class="external-link nav-link" href="https://github.com/hughjonesd/santoku/" aria-label="GitHub"><span class="fa fab fa-github fa-lg"></span></a></li>
</ul>
</div>
</div>
</nav><div class="container template-title-body">
<div class="row">
<main id="main" class="col-md-9"><div class="page-header">
<img src="https://hughjonesd.github.io/santoku/logo.png" class="logo" alt=""><h1>Page not found (404)</h1>
</div>
Content not found. Please use links in the navbar.
</main>
</div>
<footer><div class="pkgdown-footer-left">
<p>Developed by David Hugh-Jones.</p>
</div>
<div class="pkgdown-footer-right">
<p>Site built with <a href="https://pkgdown.r-lib.org/" class="external-link">pkgdown</a> 2.2.0.</p>
</div>
</footer>
</div>
</body>
</html>
================================================
FILE: docs/404.md
================================================
Content not found. Please use links in the navbar.
# Page not found (404)
================================================
FILE: docs/AGENTS.html
================================================
<!DOCTYPE html>
<!-- Generated by pkgdown: do not edit by hand --><html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"><title>AGENTS.md • santoku</title><!-- favicons --><link rel="icon" type="image/png" sizes="96x96" href="favicon-96x96.png"><link rel="icon" type="”image/svg+xml”" href="favicon.svg"><link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon.png"><link rel="icon" sizes="any" href="favicon.ico"><link rel="manifest" href="site.webmanifest"><script src="deps/jquery-3.6.0/jquery-3.6.0.min.js"></script><meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"><link href="deps/bootstrap-5.3.1/bootstrap.min.css" rel="stylesheet"><script src="deps/bootstrap-5.3.1/bootstrap.bundle.min.js"></script><link href="deps/font-awesome-6.5.2/css/all.min.css" rel="stylesheet"><link href="deps/font-awesome-6.5.2/css/v4-shims.min.css" rel="stylesheet"><script src="deps/headroom-0.11.0/headroom.min.js"></script><script src="deps/headroom-0.11.0/jQuery.headroom.min.js"></script><script src="deps/bootstrap-toc-1.0.1/bootstrap-toc.min.js"></script><script src="deps/clipboard.js-2.0.11/clipboard.min.js"></script><script src="deps/search-1.0.0/autocomplete.jquery.min.js"></script><script src="deps/search-1.0.0/fuse.min.js"></script><script src="deps/search-1.0.0/mark.min.js"></script><!-- pkgdown --><script src="pkgdown.js"></script><link href="extra.css" rel="stylesheet"><meta property="og:title" content="AGENTS.md"><meta property="og:image" content="https://hughjonesd.github.io/santoku/logo.png"></head><body>
<a href="#main" class="visually-hidden-focusable">Skip to contents</a>
<nav class="navbar navbar-expand-lg fixed-top bg-primary" data-bs-theme="dark" aria-label="Site navigation"><div class="container">
<a class="navbar-brand me-2" href="index.html">santoku</a>
<small class="nav-text text-muted me-auto" data-bs-toggle="tooltip" data-bs-placement="bottom" title="">1.2.0</small>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div id="navbar" class="collapse navbar-collapse ms-3">
<ul class="navbar-nav me-auto"><li class="nav-item"><a class="nav-link" href="articles/santoku.html">Get started</a></li>
<li class="nav-item"><a class="nav-link" href="reference/index.html">Reference</a></li>
<li class="nav-item dropdown">
<button class="nav-link dropdown-toggle" type="button" id="dropdown-articles" data-bs-toggle="dropdown" aria-expanded="false" aria-haspopup="true">Articles</button>
<ul class="dropdown-menu" aria-labelledby="dropdown-articles"><li><a class="dropdown-item" href="articles/website-articles/performance.html">Performance</a></li>
<li><a class="dropdown-item" href="articles/whats-new-in-0-9-0.html">What's new in santoku 0.9.0</a></li>
</ul></li>
<li class="nav-item dropdown">
<button class="nav-link dropdown-toggle" type="button" id="dropdown-tutorials" data-bs-toggle="dropdown" aria-expanded="false" aria-haspopup="true">Tutorials</button>
<ul class="dropdown-menu" aria-labelledby="dropdown-tutorials"><li><a class="dropdown-item" href="tutorials/00-visualintroduction.html">A visual introduction to santoku</a></li>
<li><a class="dropdown-item" href="tutorials/01-chopping-dates.html">Chopping dates with santoku</a></li>
</ul></li>
<li class="nav-item"><a class="nav-link" href="news/index.html">Changelog</a></li>
</ul><ul class="navbar-nav"><li class="nav-item"><form class="form-inline" role="search">
<input class="form-control" type="search" name="search-input" id="search-input" autocomplete="off" aria-label="Search site" placeholder="Search for" data-search-index="search.json"></form></li>
<li class="nav-item"><a class="external-link nav-link" href="https://github.com/hughjonesd/santoku/" aria-label="GitHub"><span class="fa fab fa-github fa-lg"></span></a></li>
</ul></div>
</div>
</nav><div class="container template-title-body">
<div class="row">
<main id="main" class="col-md-9"><div class="page-header">
<img src="logo.png" class="logo" alt=""><h1>AGENTS.md</h1>
<small class="dont-index">Source: <a href="https://github.com/hughjonesd/santoku/blob/HEAD/AGENTS.md" class="external-link"><code>AGENTS.md</code></a></small>
</div>
<div id="agentsmd" class="section level1">
<p>This file provides guidance to agents when working with code in this repository.</p>
<div class="section level2">
<h2 id="project-overview">Project Overview<a class="anchor" aria-label="anchor" href="#project-overview"></a></h2>
<p>santoku is an R package that provides <code><a href="reference/chop.html">chop()</a></code>, a versatile replacement for <code><a href="https://rdrr.io/r/base/cut.html" class="external-link">base::cut()</a></code> for cutting data into intervals. The package handles numeric vectors, dates, times, and other comparable objects, with support for singleton intervals and flexible labeling.</p>
</div>
<div class="section level2">
<h2 id="common-commands">Common Commands<a class="anchor" aria-label="anchor" href="#common-commands"></a></h2>
<div class="section level3">
<h3 id="testing">Testing<a class="anchor" aria-label="anchor" href="#testing"></a></h3>
<div class="sourceCode" id="cb1"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb1-1"><a href="#cb1-1" tabindex="-1"></a><span class="co"># Run all tests</span></span>
<span id="cb1-2"><a href="#cb1-2" tabindex="-1"></a>devtools<span class="sc">::</span><span class="fu">test</span>()</span>
<span id="cb1-3"><a href="#cb1-3" tabindex="-1"></a></span>
<span id="cb1-4"><a href="#cb1-4" tabindex="-1"></a><span class="co"># Run tests from command line</span></span>
<span id="cb1-5"><a href="#cb1-5" tabindex="-1"></a>R CMD check .</span>
<span id="cb1-6"><a href="#cb1-6" tabindex="-1"></a></span>
<span id="cb1-7"><a href="#cb1-7" tabindex="-1"></a><span class="co"># Run specific test file</span></span>
<span id="cb1-8"><a href="#cb1-8" tabindex="-1"></a>testthat<span class="sc">::</span><span class="fu">test_file</span>(<span class="st">"tests/testthat/test-chop.R"</span>)</span></code></pre></div>
</div>
<div class="section level3">
<h3 id="development-workflow">Development workflow<a class="anchor" aria-label="anchor" href="#development-workflow"></a></h3>
<div class="sourceCode" id="cb2"><pre class="downlit sourceCode r">
<code class="sourceCode R"><span><span class="co"># Build package</span></span>
<span><span class="fu">devtools</span><span class="fu">::</span><span class="fu"><a href="https://devtools.r-lib.org/reference/build.html" class="external-link">build</a></span><span class="op">(</span><span class="op">)</span></span>
<span></span>
<span><span class="co"># Install package locally</span></span>
<span><span class="fu">devtools</span><span class="fu">::</span><span class="fu"><a href="https://devtools.r-lib.org/reference/install.html" class="external-link">install</a></span><span class="op">(</span><span class="op">)</span></span>
<span></span>
<span><span class="co"># Check package</span></span>
<span><span class="fu">devtools</span><span class="fu">::</span><span class="fu"><a href="https://devtools.r-lib.org/reference/check.html" class="external-link">check</a></span><span class="op">(</span><span class="op">)</span></span>
<span></span>
<span><span class="co"># Load package for interactive development</span></span>
<span><span class="fu">devtools</span><span class="fu">::</span><span class="fu"><a href="https://devtools.r-lib.org/reference/load_all.html" class="external-link">load_all</a></span><span class="op">(</span><span class="op">)</span></span></code></pre></div>
</div>
<div class="section level3">
<h3 id="documentation">Documentation<a class="anchor" aria-label="anchor" href="#documentation"></a></h3>
<div class="sourceCode" id="cb3"><pre class="downlit sourceCode r">
<code class="sourceCode R"><span><span class="co"># Update documentation</span></span>
<span><span class="fu">devtools</span><span class="fu">::</span><span class="fu"><a href="https://devtools.r-lib.org/reference/document.html" class="external-link">document</a></span><span class="op">(</span><span class="op">)</span></span>
<span></span>
<span><span class="co"># Build website</span></span>
<span><span class="fu">pkgdown</span><span class="fu">::</span><span class="fu"><a href="https://pkgdown.r-lib.org/reference/build_site.html" class="external-link">build_site</a></span><span class="op">(</span><span class="op">)</span></span></code></pre></div>
</div>
</div>
<div class="section level2">
<h2 id="architecture">Architecture<a class="anchor" aria-label="anchor" href="#architecture"></a></h2>
<div class="section level3">
<h3 id="core-components">Core Components<a class="anchor" aria-label="anchor" href="#core-components"></a></h3>
<ul><li>
<strong>Main cutting function</strong>: <code><a href="reference/chop.html">chop()</a></code> in <code>R/chop.R</code> - the primary interface that calls other functions</li>
<li>
<strong>Break creation</strong>: <code>R/breaks*.R</code> files contain functions to create break points (<code>brk_*</code> functions)</li>
<li>
<strong>Labeling system</strong>: <code>R/labels*.R</code> files contain labeling functions (<code>lbl_*</code> functions)<br></li>
<li>
<strong>Convenience functions</strong>: <code>R/chop-*.R</code> files contain <code>chop_*</code> wrapper functions for common use cases</li>
<li>
<strong>C++ backend</strong>: <code>src/categorize.cpp</code> provides fast interval categorization via Rcpp</li>
<li>
<strong>Tabulation</strong>: <code>R/tab.R</code> provides <code>tab_*</code> functions that chop and tabulate in one step</li>
</ul></div>
<div class="section level3">
<h3 id="key-design-patterns">Key Design Patterns<a class="anchor" aria-label="anchor" href="#key-design-patterns"></a></h3>
<ol style="list-style-type: decimal"><li>
<strong>Function factories</strong>: Many functions return other functions (e.g., <code>brk_*</code> functions return break-creation functions)</li>
<li>
<strong>Method dispatch</strong>: Uses S3 methods and vctrs for handling different data types (numbers, dates, etc.)</li>
<li>
<strong>Extensible labeling</strong>: Label functions can be combined and customized using the <code>lbl_*</code> family</li>
<li>
<strong>Performance</strong>: Core categorization logic is implemented in C++ for speed</li>
</ol></div>
<div class="section level3">
<h3 id="file-organization">File Organization<a class="anchor" aria-label="anchor" href="#file-organization"></a></h3>
<ul><li>
<code>R/chop.R</code> - Main <code><a href="reference/chop.html">chop()</a></code> function and documentation</li>
<li>
<code>R/breaks*.R</code> - Break point creation (<code>brk_default</code>, <code>brk_width</code>, etc.)</li>
<li>
<code>R/labels*.R</code> - Label generation (<code>lbl_intervals</code>, <code>lbl_dash</code>, etc.)</li>
<li>
<code>R/chop-*.R</code> - Convenience functions (<code>chop_quantiles</code>, <code>chop_width</code>, etc.)</li>
<li>
<code>R/tab.R</code> - Tabulation functions</li>
<li>
<code>R/utils.R</code> - Utility functions like <code><a href="reference/exactly.html">exactly()</a></code> and <code><a href="reference/percent.html">percent()</a></code>
</li>
<li>
<code>src/categorize.cpp</code> - Fast C++ categorization implementation</li>
<li>
<code>tests/testthat/</code> - Comprehensive test suite</li>
</ul></div>
</div>
<div class="section level2">
<h2 id="development-notes">Development Notes<a class="anchor" aria-label="anchor" href="#development-notes"></a></h2>
<ul><li>The package uses Rcpp for performance-critical categorization</li>
<li>Tests are extensive and include systematic testing in <code>test-zzz-systematic.R</code>
</li>
<li>The package supports non-standard data types (dates, times, units) via the vctrs package</li>
<li>Documentation follows roxygen2 conventions with extensive examples</li>
<li>Uses lifecycle package for function lifecycle management</li>
</ul></div>
</div>
</main><aside class="col-md-3"><nav id="toc" aria-label="Table of contents"><h2>On this page</h2>
</nav></aside></div>
<footer><div class="pkgdown-footer-left">
<p>Developed by David Hugh-Jones.</p>
</div>
<div class="pkgdown-footer-right">
<p>Site built with <a href="https://pkgdown.r-lib.org/" class="external-link">pkgdown</a> 2.2.0.</p>
</div>
</footer></div>
</body></html>
================================================
FILE: docs/AGENTS.md
================================================
# AGENTS.md
This file provides guidance to agents when working with code in this
repository.
## Project Overview
santoku is an R package that provides
[`chop()`](https://hughjonesd.github.io/santoku/reference/chop.md), a
versatile replacement for
[`base::cut()`](https://rdrr.io/r/base/cut.html) for cutting data into
intervals. The package handles numeric vectors, dates, times, and other
comparable objects, with support for singleton intervals and flexible
labeling.
## Common Commands
### Testing
``` r
# Run all tests
devtools::test()
# Run tests from command line
R CMD check .
# Run specific test file
testthat::test_file("tests/testthat/test-chop.R")
```
### Development workflow
``` r
# Build package
devtools::build()
# Install package locally
devtools::install()
# Check package
devtools::check()
# Load package for interactive development
devtools::load_all()
```
### Documentation
``` r
# Update documentation
devtools::document()
# Build website
pkgdown::build_site()
```
## Architecture
### Core Components
- **Main cutting function**:
[`chop()`](https://hughjonesd.github.io/santoku/reference/chop.md) in
`R/chop.R` - the primary interface that calls other functions
- **Break creation**: `R/breaks*.R` files contain functions to create
break points (`brk_*` functions)
- **Labeling system**: `R/labels*.R` files contain labeling functions
(`lbl_*` functions)
- **Convenience functions**: `R/chop-*.R` files contain `chop_*` wrapper
functions for common use cases
- **C++ backend**: `src/categorize.cpp` provides fast interval
categorization via Rcpp
- **Tabulation**: `R/tab.R` provides `tab_*` functions that chop and
tabulate in one step
### Key Design Patterns
1. **Function factories**: Many functions return other functions (e.g.,
`brk_*` functions return break-creation functions)
2. **Method dispatch**: Uses S3 methods and vctrs for handling
different data types (numbers, dates, etc.)
3. **Extensible labeling**: Label functions can be combined and
customized using the `lbl_*` family
4. **Performance**: Core categorization logic is implemented in C++ for
speed
### File Organization
- `R/chop.R` - Main
[`chop()`](https://hughjonesd.github.io/santoku/reference/chop.md)
function and documentation
- `R/breaks*.R` - Break point creation (`brk_default`, `brk_width`,
etc.)
- `R/labels*.R` - Label generation (`lbl_intervals`, `lbl_dash`, etc.)
- `R/chop-*.R` - Convenience functions (`chop_quantiles`, `chop_width`,
etc.)
- `R/tab.R` - Tabulation functions
- `R/utils.R` - Utility functions like
[`exactly()`](https://hughjonesd.github.io/santoku/reference/exactly.md)
and
[`percent()`](https://hughjonesd.github.io/santoku/reference/percent.md)
- `src/categorize.cpp` - Fast C++ categorization implementation
- `tests/testthat/` - Comprehensive test suite
## Development Notes
- The package uses Rcpp for performance-critical categorization
- Tests are extensive and include systematic testing in
`test-zzz-systematic.R`
- The package supports non-standard data types (dates, times, units) via
the vctrs package
- Documentation follows roxygen2 conventions with extensive examples
- Uses lifecycle package for function lifecycle management
================================================
FILE: docs/CLAUDE.html
================================================
<!DOCTYPE html>
<!-- Generated by pkgdown: do not edit by hand --><html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"><title>CLAUDE.md • santoku</title><!-- favicons --><link rel="icon" type="image/png" sizes="96x96" href="favicon-96x96.png"><link rel="icon" type="”image/svg+xml”" href="favicon.svg"><link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon.png"><link rel="icon" sizes="any" href="favicon.ico"><link rel="manifest" href="site.webmanifest"><script src="deps/jquery-3.6.0/jquery-3.6.0.min.js"></script><meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"><link href="deps/bootstrap-5.3.1/bootstrap.min.css" rel="stylesheet"><script src="deps/bootstrap-5.3.1/bootstrap.bundle.min.js"></script><link href="deps/font-awesome-6.5.2/css/all.min.css" rel="stylesheet"><link href="deps/font-awesome-6.5.2/css/v4-shims.min.css" rel="stylesheet"><script src="deps/headroom-0.11.0/headroom.min.js"></script><script src="deps/headroom-0.11.0/jQuery.headroom.min.js"></script><script src="deps/bootstrap-toc-1.0.1/bootstrap-toc.min.js"></script><script src="deps/clipboard.js-2.0.11/clipboard.min.js"></script><script src="deps/search-1.0.0/autocomplete.jquery.min.js"></script><script src="deps/search-1.0.0/fuse.min.js"></script><script src="deps/search-1.0.0/mark.min.js"></script><!-- pkgdown --><script src="pkgdown.js"></script><link href="extra.css" rel="stylesheet"><meta property="og:title" content="CLAUDE.md"><meta property="og:image" content="https://hughjonesd.github.io/santoku/logo.png"></head><body>
<a href="#main" class="visually-hidden-focusable">Skip to contents</a>
<nav class="navbar navbar-expand-lg fixed-top bg-primary" data-bs-theme="dark" aria-label="Site navigation"><div class="container">
<a class="navbar-brand me-2" href="index.html">santoku</a>
<small class="nav-text text-muted me-auto" data-bs-toggle="tooltip" data-bs-placement="bottom" title="">1.1.0</small>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div id="navbar" class="collapse navbar-collapse ms-3">
<ul class="navbar-nav me-auto"><li class="nav-item"><a class="nav-link" href="articles/santoku.html">Get started</a></li>
<li class="nav-item"><a class="nav-link" href="reference/index.html">Reference</a></li>
<li class="nav-item dropdown">
<button class="nav-link dropdown-toggle" type="button" id="dropdown-articles" data-bs-toggle="dropdown" aria-expanded="false" aria-haspopup="true">Articles</button>
<ul class="dropdown-menu" aria-labelledby="dropdown-articles"><li><a class="dropdown-item" href="articles/website-articles/performance.html">Performance</a></li>
<li><a class="dropdown-item" href="articles/whats-new-in-0-9-0.html">What's new in santoku 0.9.0</a></li>
</ul></li>
<li class="nav-item dropdown">
<button class="nav-link dropdown-toggle" type="button" id="dropdown-tutorials" data-bs-toggle="dropdown" aria-expanded="false" aria-haspopup="true">Tutorials</button>
<ul class="dropdown-menu" aria-labelledby="dropdown-tutorials"><li><a class="dropdown-item" href="tutorials/00-visualintroduction.html">A visual introduction to santoku</a></li>
<li><a class="dropdown-item" href="tutorials/01-chopping-dates.html">Chopping dates with santoku</a></li>
</ul></li>
<li class="nav-item"><a class="nav-link" href="news/index.html">Changelog</a></li>
</ul><ul class="navbar-nav"><li class="nav-item"><form class="form-inline" role="search">
<input class="form-control" type="search" name="search-input" id="search-input" autocomplete="off" aria-label="Search site" placeholder="Search for" data-search-index="search.json"></form></li>
<li class="nav-item"><a class="external-link nav-link" href="https://github.com/hughjonesd/santoku/" aria-label="GitHub"><span class="fa fab fa-github fa-lg"></span></a></li>
</ul></div>
</div>
</nav><div class="container template-title-body">
<div class="row">
<main id="main" class="col-md-9"><div class="page-header">
<img src="logo.png" class="logo" alt=""><h1>CLAUDE.md</h1>
<small class="dont-index">Source: <a href="https://github.com/hughjonesd/santoku/blob/HEAD/CLAUDE.md" class="external-link"><code>CLAUDE.md</code></a></small>
</div>
<div id="claudemd" class="section level1">
<p>This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.</p>
<div class="section level2">
<h2 id="project-overview">Project Overview<a class="anchor" aria-label="anchor" href="#project-overview"></a></h2>
<p>santoku is an R package that provides <code><a href="reference/chop.html">chop()</a></code>, a versatile replacement for <code><a href="https://rdrr.io/r/base/cut.html" class="external-link">base::cut()</a></code> for cutting data into intervals. The package handles numeric vectors, dates, times, and other comparable objects, with support for singleton intervals and flexible labeling.</p>
</div>
<div class="section level2">
<h2 id="common-commands">Common Commands<a class="anchor" aria-label="anchor" href="#common-commands"></a></h2>
<div class="section level3">
<h3 id="testing">Testing<a class="anchor" aria-label="anchor" href="#testing"></a></h3>
<div class="sourceCode" id="cb1"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb1-1"><a href="#cb1-1" tabindex="-1"></a><span class="co"># Run all tests</span></span>
<span id="cb1-2"><a href="#cb1-2" tabindex="-1"></a>devtools<span class="sc">::</span><span class="fu">test</span>()</span>
<span id="cb1-3"><a href="#cb1-3" tabindex="-1"></a></span>
<span id="cb1-4"><a href="#cb1-4" tabindex="-1"></a><span class="co"># Run tests from command line</span></span>
<span id="cb1-5"><a href="#cb1-5" tabindex="-1"></a>R CMD check .</span>
<span id="cb1-6"><a href="#cb1-6" tabindex="-1"></a></span>
<span id="cb1-7"><a href="#cb1-7" tabindex="-1"></a><span class="co"># Run specific test file</span></span>
<span id="cb1-8"><a href="#cb1-8" tabindex="-1"></a>testthat<span class="sc">::</span><span class="fu">test_file</span>(<span class="st">"tests/testthat/test-chop.R"</span>)</span></code></pre></div>
</div>
<div class="section level3">
<h3 id="development-workflow">Development workflow<a class="anchor" aria-label="anchor" href="#development-workflow"></a></h3>
<div class="sourceCode" id="cb2"><pre class="downlit sourceCode r">
<code class="sourceCode R"><span><span class="co"># Build package</span></span>
<span><span class="fu">devtools</span><span class="fu">::</span><span class="fu"><a href="https://devtools.r-lib.org/reference/build.html" class="external-link">build</a></span><span class="op">(</span><span class="op">)</span></span>
<span></span>
<span><span class="co"># Install package locally</span></span>
<span><span class="fu">devtools</span><span class="fu">::</span><span class="fu"><a href="https://devtools.r-lib.org/reference/install.html" class="external-link">install</a></span><span class="op">(</span><span class="op">)</span></span>
<span></span>
<span><span class="co"># Check package</span></span>
<span><span class="fu">devtools</span><span class="fu">::</span><span class="fu"><a href="https://devtools.r-lib.org/reference/check.html" class="external-link">check</a></span><span class="op">(</span><span class="op">)</span></span>
<span></span>
<span><span class="co"># Load package for interactive development</span></span>
<span><span class="fu">devtools</span><span class="fu">::</span><span class="fu"><a href="https://devtools.r-lib.org/reference/load_all.html" class="external-link">load_all</a></span><span class="op">(</span><span class="op">)</span></span></code></pre></div>
</div>
<div class="section level3">
<h3 id="documentation">Documentation<a class="anchor" aria-label="anchor" href="#documentation"></a></h3>
<div class="sourceCode" id="cb3"><pre class="downlit sourceCode r">
<code class="sourceCode R"><span><span class="co"># Update documentation</span></span>
<span><span class="fu">devtools</span><span class="fu">::</span><span class="fu"><a href="https://devtools.r-lib.org/reference/document.html" class="external-link">document</a></span><span class="op">(</span><span class="op">)</span></span>
<span></span>
<span><span class="co"># Build website</span></span>
<span><span class="fu">pkgdown</span><span class="fu">::</span><span class="fu"><a href="https://pkgdown.r-lib.org/reference/build_site.html" class="external-link">build_site</a></span><span class="op">(</span><span class="op">)</span></span></code></pre></div>
</div>
</div>
<div class="section level2">
<h2 id="architecture">Architecture<a class="anchor" aria-label="anchor" href="#architecture"></a></h2>
<div class="section level3">
<h3 id="core-components">Core Components<a class="anchor" aria-label="anchor" href="#core-components"></a></h3>
<ul><li>
<strong>Main cutting function</strong>: <code><a href="reference/chop.html">chop()</a></code> in <code>R/chop.R</code> - the primary interface that calls other functions</li>
<li>
<strong>Break creation</strong>: <code>R/breaks*.R</code> files contain functions to create break points (<code>brk_*</code> functions)</li>
<li>
<strong>Labeling system</strong>: <code>R/labels*.R</code> files contain labeling functions (<code>lbl_*</code> functions)<br></li>
<li>
<strong>Convenience functions</strong>: <code>R/chop-*.R</code> files contain <code>chop_*</code> wrapper functions for common use cases</li>
<li>
<strong>C++ backend</strong>: <code>src/categorize.cpp</code> provides fast interval categorization via Rcpp</li>
<li>
<strong>Tabulation</strong>: <code>R/tab.R</code> provides <code>tab_*</code> functions that chop and tabulate in one step</li>
</ul></div>
<div class="section level3">
<h3 id="key-design-patterns">Key Design Patterns<a class="anchor" aria-label="anchor" href="#key-design-patterns"></a></h3>
<ol style="list-style-type: decimal"><li>
<strong>Function factories</strong>: Many functions return other functions (e.g., <code>brk_*</code> functions return break-creation functions)</li>
<li>
<strong>Method dispatch</strong>: Uses S3 methods and vctrs for handling different data types (numbers, dates, etc.)</li>
<li>
<strong>Extensible labeling</strong>: Label functions can be combined and customized using the <code>lbl_*</code> family</li>
<li>
<strong>Performance</strong>: Core categorization logic is implemented in C++ for speed</li>
</ol></div>
<div class="section level3">
<h3 id="file-organization">File Organization<a class="anchor" aria-label="anchor" href="#file-organization"></a></h3>
<ul><li>
<code>R/chop.R</code> - Main <code><a href="reference/chop.html">chop()</a></code> function and documentation</li>
<li>
<code>R/breaks*.R</code> - Break point creation (<code>brk_default</code>, <code>brk_width</code>, etc.)</li>
<li>
<code>R/labels*.R</code> - Label generation (<code>lbl_intervals</code>, <code>lbl_dash</code>, etc.)</li>
<li>
<code>R/chop-*.R</code> - Convenience functions (<code>chop_quantiles</code>, <code>chop_width</code>, etc.)</li>
<li>
<code>R/tab.R</code> - Tabulation functions</li>
<li>
<code>R/utils.R</code> - Utility functions like <code><a href="reference/exactly.html">exactly()</a></code> and <code><a href="reference/percent.html">percent()</a></code>
</li>
<li>
<code>src/categorize.cpp</code> - Fast C++ categorization implementation</li>
<li>
<code>tests/testthat/</code> - Comprehensive test suite</li>
</ul></div>
</div>
<div class="section level2">
<h2 id="development-notes">Development Notes<a class="anchor" aria-label="anchor" href="#development-notes"></a></h2>
<ul><li>The package uses Rcpp for performance-critical categorization</li>
<li>Tests are extensive and include systematic testing in <code>test-zzz-systematic.R</code>
</li>
<li>The package supports non-standard data types (dates, times, units) via the vctrs package</li>
<li>Documentation follows roxygen2 conventions with extensive examples</li>
<li>Uses lifecycle package for function lifecycle management</li>
</ul></div>
</div>
</main><aside class="col-md-3"><nav id="toc" aria-label="Table of contents"><h2>On this page</h2>
</nav></aside></div>
<footer><div class="pkgdown-footer-left">
<p>Developed by David Hugh-Jones.</p>
</div>
<div class="pkgdown-footer-right">
<p>Site built with <a href="https://pkgdown.r-lib.org/" class="external-link">pkgdown</a> 2.2.0.</p>
</div>
</footer></div>
</body></html>
================================================
FILE: docs/CLAUDE.md
================================================
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working
with code in this repository.
## Project Overview
santoku is an R package that provides
[`chop()`](https://hughjonesd.github.io/santoku/reference/chop.md), a
versatile replacement for
[`base::cut()`](https://rdrr.io/r/base/cut.html) for cutting data into
intervals. The package handles numeric vectors, dates, times, and other
comparable objects, with support for singleton intervals and flexible
labeling.
## Common Commands
### Testing
``` r
# Run all tests
devtools::test()
# Run tests from command line
R CMD check .
# Run specific test file
testthat::test_file("tests/testthat/test-chop.R")
```
### Development workflow
``` r
# Build package
devtools::build()
# Install package locally
devtools::install()
# Check package
devtools::check()
# Load package for interactive development
devtools::load_all()
```
### Documentation
``` r
# Update documentation
devtools::document()
# Build website
pkgdown::build_site()
```
## Architecture
### Core Components
- **Main cutting function**:
[`chop()`](https://hughjonesd.github.io/santoku/reference/chop.md) in
`R/chop.R` - the primary interface that calls other functions
- **Break creation**: `R/breaks*.R` files contain functions to create
break points (`brk_*` functions)
- **Labeling system**: `R/labels*.R` files contain labeling functions
(`lbl_*` functions)
- **Convenience functions**: `R/chop-*.R` files contain `chop_*` wrapper
functions for common use cases
- **C++ backend**: `src/categorize.cpp` provides fast interval
categorization via Rcpp
- **Tabulation**: `R/tab.R` provides `tab_*` functions that chop and
tabulate in one step
### Key Design Patterns
1. **Function factories**: Many functions return other functions (e.g.,
`brk_*` functions return break-creation functions)
2. **Method dispatch**: Uses S3 methods and vctrs for handling
different data types (numbers, dates, etc.)
3. **Extensible labeling**: Label functions can be combined and
customized using the `lbl_*` family
4. **Performance**: Core categorization logic is implemented in C++ for
speed
### File Organization
- `R/chop.R` - Main
[`chop()`](https://hughjonesd.github.io/santoku/reference/chop.md)
function and documentation
- `R/breaks*.R` - Break point creation (`brk_default`, `brk_width`,
etc.)
- `R/labels*.R` - Label generation (`lbl_intervals`, `lbl_dash`, etc.)
- `R/chop-*.R` - Convenience functions (`chop_quantiles`, `chop_width`,
etc.)
- `R/tab.R` - Tabulation functions
- `R/utils.R` - Utility functions like
[`exactly()`](https://hughjonesd.github.io/santoku/reference/exactly.md)
and
[`percent()`](https://hughjonesd.github.io/santoku/reference/percent.md)
- `src/categorize.cpp` - Fast C++ categorization implementation
- `tests/testthat/` - Comprehensive test suite
## Development Notes
- The package uses Rcpp for performance-critical categorization
- Tests are extensive and include systematic testing in
`test-zzz-systematic.R`
- The package supports non-standard data types (dates, times, units) via
the vctrs package
- Documentation follows roxygen2 conventions with extensive examples
- Uses lifecycle package for function lifecycle management
================================================
FILE: docs/LICENSE-text.html
================================================
<!DOCTYPE html>
<!-- Generated by pkgdown: do not edit by hand --><html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"><title>License • santoku</title><!-- favicons --><link rel="icon" type="image/png" sizes="96x96" href="favicon-96x96.png"><link rel="icon" type="”image/svg+xml”" href="favicon.svg"><link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon.png"><link rel="icon" sizes="any" href="favicon.ico"><link rel="manifest" href="site.webmanifest"><script src="deps/jquery-3.6.0/jquery-3.6.0.min.js"></script><meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"><link href="deps/bootstrap-5.3.1/bootstrap.min.css" rel="stylesheet"><script src="deps/bootstrap-5.3.1/bootstrap.bundle.min.js"></script><link href="deps/font-awesome-6.5.2/css/all.min.css" rel="stylesheet"><link href="deps/font-awesome-6.5.2/css/v4-shims.min.css" rel="stylesheet"><script src="deps/headroom-0.11.0/headroom.min.js"></script><script src="deps/headroom-0.11.0/jQuery.headroom.min.js"></script><script src="deps/bootstrap-toc-1.0.1/bootstrap-toc.min.js"></script><script src="deps/clipboard.js-2.0.11/clipboard.min.js"></script><script src="deps/search-1.0.0/autocomplete.jquery.min.js"></script><script src="deps/search-1.0.0/fuse.min.js"></script><script src="deps/search-1.0.0/mark.min.js"></script><!-- pkgdown --><script src="pkgdown.js"></script><link href="extra.css" rel="stylesheet"><meta property="og:title" content="License"><meta property="og:image" content="https://hughjonesd.github.io/santoku/logo.png"></head><body>
<a href="#main" class="visually-hidden-focusable">Skip to contents</a>
<nav class="navbar navbar-expand-lg fixed-top bg-primary" data-bs-theme="dark" aria-label="Site navigation"><div class="container">
<a class="navbar-brand me-2" href="index.html">santoku</a>
<small class="nav-text text-muted me-auto" data-bs-toggle="tooltip" data-bs-placement="bottom" title="">1.2.0</small>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div id="navbar" class="collapse navbar-collapse ms-3">
<ul class="navbar-nav me-auto"><li class="nav-item"><a class="nav-link" href="articles/santoku.html">Get started</a></li>
<li class="nav-item"><a class="nav-link" href="reference/index.html">Reference</a></li>
<li class="nav-item dropdown">
<button class="nav-link dropdown-toggle" type="button" id="dropdown-articles" data-bs-toggle="dropdown" aria-expanded="false" aria-haspopup="true">Articles</button>
<ul class="dropdown-menu" aria-labelledby="dropdown-articles"><li><a class="dropdown-item" href="articles/website-articles/performance.html">Performance</a></li>
<li><a class="dropdown-item" href="articles/whats-new-in-0-9-0.html">What's new in santoku 0.9.0</a></li>
</ul></li>
<li class="nav-item dropdown">
<button class="nav-link dropdown-toggle" type="button" id="dropdown-tutorials" data-bs-toggle="dropdown" aria-expanded="false" aria-haspopup="true">Tutorials</button>
<ul class="dropdown-menu" aria-labelledby="dropdown-tutorials"><li><a class="dropdown-item" href="tutorials/00-visualintroduction.html">A visual introduction to santoku</a></li>
<li><a class="dropdown-item" href="tutorials/01-chopping-dates.html">Chopping dates with santoku</a></li>
</ul></li>
<li class="nav-item"><a class="nav-link" href="news/index.html">Changelog</a></li>
</ul><ul class="navbar-nav"><li class="nav-item"><form class="form-inline" role="search">
<input class="form-control" type="search" name="search-input" id="search-input" autocomplete="off" aria-label="Search site" placeholder="Search for" data-search-index="search.json"></form></li>
<li class="nav-item"><a class="external-link nav-link" href="https://github.com/hughjonesd/santoku/" aria-label="GitHub"><span class="fa fab fa-github fa-lg"></span></a></li>
</ul></div>
</div>
</nav><div class="container template-title-body">
<div class="row">
<main id="main" class="col-md-9"><div class="page-header">
<img src="logo.png" class="logo" alt=""><h1>License</h1>
</div>
<pre>YEAR: 2020
COPYRIGHT HOLDER: David Hugh-Jones
</pre>
</main></div>
<footer><div class="pkgdown-footer-left">
<p>Developed by David Hugh-Jones.</p>
</div>
<div class="pkgdown-footer-right">
<p>Site built with <a href="https://pkgdown.r-lib.org/" class="external-link">pkgdown</a> 2.2.0.</p>
</div>
</footer></div>
</body></html>
================================================
FILE: docs/LICENSE-text.md
================================================
# License
YEAR: 2020
COPYRIGHT HOLDER: David Hugh-Jones
================================================
FILE: docs/LICENSE.html
================================================
<!DOCTYPE html>
<!-- Generated by pkgdown: do not edit by hand --><html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"><title>MIT License • santoku</title><!-- favicons --><link rel="icon" type="image/png" sizes="96x96" href="favicon-96x96.png"><link rel="icon" type="”image/svg+xml”" href="favicon.svg"><link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon.png"><link rel="icon" sizes="any" href="favicon.ico"><link rel="manifest" href="site.webmanifest"><script src="deps/jquery-3.6.0/jquery-3.6.0.min.js"></script><meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"><link href="deps/bootstrap-5.3.1/bootstrap.min.css" rel="stylesheet"><script src="deps/bootstrap-5.3.1/bootstrap.bundle.min.js"></script><link href="deps/font-awesome-6.5.2/css/all.min.css" rel="stylesheet"><link href="deps/font-awesome-6.5.2/css/v4-shims.min.css" rel="stylesheet"><script src="deps/headroom-0.11.0/headroom.min.js"></script><script src="deps/headroom-0.11.0/jQuery.headroom.min.js"></script><script src="deps/bootstrap-toc-1.0.1/bootstrap-toc.min.js"></script><script src="deps/clipboard.js-2.0.11/clipboard.min.js"></script><script src="deps/search-1.0.0/autocomplete.jquery.min.js"></script><script src="deps/search-1.0.0/fuse.min.js"></script><script src="deps/search-1.0.0/mark.min.js"></script><!-- pkgdown --><script src="pkgdown.js"></script><link href="extra.css" rel="stylesheet"><meta property="og:title" content="MIT License"><meta property="og:image" content="https://hughjonesd.github.io/santoku/logo.png"></head><body>
<a href="#main" class="visually-hidden-focusable">Skip to contents</a>
<nav class="navbar navbar-expand-lg fixed-top bg-primary" data-bs-theme="dark" aria-label="Site navigation"><div class="container">
<a class="navbar-brand me-2" href="index.html">santoku</a>
<small class="nav-text text-muted me-auto" data-bs-toggle="tooltip" data-bs-placement="bottom" title="">1.2.0</small>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div id="navbar" class="collapse navbar-collapse ms-3">
<ul class="navbar-nav me-auto"><li class="nav-item"><a class="nav-link" href="articles/santoku.html">Get started</a></li>
<li class="nav-item"><a class="nav-link" href="reference/index.html">Reference</a></li>
<li class="nav-item dropdown">
<button class="nav-link dropdown-toggle" type="button" id="dropdown-articles" data-bs-toggle="dropdown" aria-expanded="false" aria-haspopup="true">Articles</button>
<ul class="dropdown-menu" aria-labelledby="dropdown-articles"><li><a class="dropdown-item" href="articles/website-articles/performance.html">Performance</a></li>
<li><a class="dropdown-item" href="articles/whats-new-in-0-9-0.html">What's new in santoku 0.9.0</a></li>
</ul></li>
<li class="nav-item dropdown">
<button class="nav-link dropdown-toggle" type="button" id="dropdown-tutorials" data-bs-toggle="dropdown" aria-expanded="false" aria-haspopup="true">Tutorials</button>
<ul class="dropdown-menu" aria-labelledby="dropdown-tutorials"><li><a class="dropdown-item" href="tutorials/00-visualintroduction.html">A visual introduction to santoku</a></li>
<li><a class="dropdown-item" href="tutorials/01-chopping-dates.html">Chopping dates with santoku</a></li>
</ul></li>
<li class="nav-item"><a class="nav-link" href="news/index.html">Changelog</a></li>
</ul><ul class="navbar-nav"><li class="nav-item"><form class="form-inline" role="search">
<input class="form-control" type="search" name="search-input" id="search-input" autocomplete="off" aria-label="Search site" placeholder="Search for" data-search-index="search.json"></form></li>
<li class="nav-item"><a class="external-link nav-link" href="https://github.com/hughjonesd/santoku/" aria-label="GitHub"><span class="fa fab fa-github fa-lg"></span></a></li>
</ul></div>
</div>
</nav><div class="container template-title-body">
<div class="row">
<main id="main" class="col-md-9"><div class="page-header">
<img src="logo.png" class="logo" alt=""><h1>MIT License</h1>
<small class="dont-index">Source: <a href="https://github.com/hughjonesd/santoku/blob/HEAD/LICENSE.md" class="external-link"><code>LICENSE.md</code></a></small>
</div>
<div id="mit-license" class="section level1">
<p>Copyright (c) 2019 David Hugh-Jones</p>
<p>Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:</p>
<p>The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.</p>
<p>THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</p>
</div>
</main></div>
<footer><div class="pkgdown-footer-left">
<p>Developed by David Hugh-Jones.</p>
</div>
<div class="pkgdown-footer-right">
<p>Site built with <a href="https://pkgdown.r-lib.org/" class="external-link">pkgdown</a> 2.2.0.</p>
</div>
</footer></div>
</body></html>
================================================
FILE: docs/LICENSE.md
================================================
# MIT License
Copyright (c) 2019 David Hugh-Jones
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
“Software”), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: docs/TODO.html
================================================
<!DOCTYPE html>
<!-- Generated by pkgdown: do not edit by hand --><html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-
gitextract_5kkl1r2r/
├── .Rbuildignore
├── .github/
│ ├── .gitignore
│ └── workflows/
│ ├── R-CMD-check.yaml
│ └── test-coverage.yaml
├── .gitignore
├── AGENTS.md
├── DESCRIPTION
├── LICENSE
├── LICENSE.md
├── NAMESPACE
├── NEWS.md
├── R/
│ ├── RcppExports.R
│ ├── breaks-by-group-size.R
│ ├── breaks-by-width.R
│ ├── breaks-impl.R
│ ├── breaks-misc.R
│ ├── breaks.R
│ ├── categorize.R
│ ├── chop-by-group-size.R
│ ├── chop-by-width.R
│ ├── chop-isolates.R
│ ├── chop-misc.R
│ ├── chop.R
│ ├── labels-datetime.R
│ ├── labels-glue.R
│ ├── labels-impl.R
│ ├── labels-single.R
│ ├── labels.R
│ ├── non-standard-types-doc.R
│ ├── santoku-cast.R
│ ├── santoku-package.R
│ ├── tab.R
│ └── utils.R
├── README.Rmd
├── README.md
├── TODO.md
├── _pkgdown.yml
├── advantages.Rmd
├── codecov.yml
├── cran-comments.md
├── docs/
│ ├── 404.html
│ ├── 404.md
│ ├── AGENTS.html
│ ├── AGENTS.md
│ ├── CLAUDE.html
│ ├── CLAUDE.md
│ ├── LICENSE-text.html
│ ├── LICENSE-text.md
│ ├── LICENSE.html
│ ├── LICENSE.md
│ ├── TODO.html
│ ├── TODO.md
│ ├── articles/
│ │ ├── index.html
│ │ ├── index.md
│ │ ├── santoku.html
│ │ ├── santoku.md
│ │ ├── santoku_files/
│ │ │ ├── accessible-code-block-0.0.1/
│ │ │ │ └── empty-anchor.js
│ │ │ ├── header-attrs-2.11/
│ │ │ │ └── header-attrs.js
│ │ │ └── header-attrs-2.8/
│ │ │ └── header-attrs.js
│ │ ├── website-articles/
│ │ │ ├── performance.html
│ │ │ ├── performance.md
│ │ │ └── performance_files/
│ │ │ ├── accessible-code-block-0.0.1/
│ │ │ │ └── empty-anchor.js
│ │ │ ├── header-attrs-2.11/
│ │ │ │ └── header-attrs.js
│ │ │ └── header-attrs-2.8/
│ │ │ └── header-attrs.js
│ │ ├── whats-new-in-0-9-0.html
│ │ └── whats-new-in-0-9-0.md
│ ├── authors.html
│ ├── authors.md
│ ├── bootstrap-toc.css
│ ├── bootstrap-toc.js
│ ├── deps/
│ │ ├── _Courier Prime-0.4.0/
│ │ │ └── font.css
│ │ ├── bootstrap-5.1.0/
│ │ │ └── font.css
│ │ ├── bootstrap-5.1.3/
│ │ │ └── font.css
│ │ ├── bootstrap-5.2.2/
│ │ │ └── font.css
│ │ ├── bootstrap-5.3.1/
│ │ │ └── font.css
│ │ ├── data-deps.txt
│ │ ├── font-awesome-6.5.2/
│ │ │ └── css/
│ │ │ ├── all.css
│ │ │ └── v4-shims.css
│ │ └── jquery-3.6.0/
│ │ └── jquery-3.6.0.js
│ ├── docsearch.css
│ ├── docsearch.js
│ ├── extra.css
│ ├── index.html
│ ├── index.md
│ ├── katex-auto.js
│ ├── lightswitch.js
│ ├── llms.txt
│ ├── news/
│ │ ├── index.html
│ │ └── index.md
│ ├── pkgdown.css
│ ├── pkgdown.js
│ ├── pkgdown.yml
│ ├── reference/
│ │ ├── breaks-class.html
│ │ ├── breaks-class.md
│ │ ├── brk-left-right.html
│ │ ├── brk-left-right.md
│ │ ├── brk_default.html
│ │ ├── brk_default.md
│ │ ├── brk_equally.html
│ │ ├── brk_evenly.html
│ │ ├── brk_fn.html
│ │ ├── brk_manual.html
│ │ ├── brk_manual.md
│ │ ├── brk_mean_sd.html
│ │ ├── brk_n.html
│ │ ├── brk_pretty.html
│ │ ├── brk_proportions.html
│ │ ├── brk_quantiles.html
│ │ ├── brk_spikes.html
│ │ ├── brk_width-for-datetime.html
│ │ ├── brk_width-for-datetime.md
│ │ ├── brk_width.Duration.html
│ │ ├── brk_width.default.html
│ │ ├── brk_width.html
│ │ ├── chop.html
│ │ ├── chop.md
│ │ ├── chop_deciles.html
│ │ ├── chop_equally.html
│ │ ├── chop_equally.md
│ │ ├── chop_evenly.html
│ │ ├── chop_evenly.md
│ │ ├── chop_fn.html
│ │ ├── chop_fn.md
│ │ ├── chop_mean_sd.html
│ │ ├── chop_mean_sd.md
│ │ ├── chop_n.html
│ │ ├── chop_n.md
│ │ ├── chop_pretty.html
│ │ ├── chop_pretty.md
│ │ ├── chop_proportions.html
│ │ ├── chop_proportions.md
│ │ ├── chop_quantiles.html
│ │ ├── chop_quantiles.md
│ │ ├── chop_spikes.html
│ │ ├── chop_spikes.md
│ │ ├── chop_width.html
│ │ ├── chop_width.md
│ │ ├── dissect.html
│ │ ├── dissect.md
│ │ ├── exactly.html
│ │ ├── exactly.md
│ │ ├── fillet.html
│ │ ├── fillet.md
│ │ ├── format.breaks.html
│ │ ├── index.html
│ │ ├── index.md
│ │ ├── is.breaks.html
│ │ ├── kiru.html
│ │ ├── knife.html
│ │ ├── knife.md
│ │ ├── lbl_dash.html
│ │ ├── lbl_dash.md
│ │ ├── lbl_datetime.html
│ │ ├── lbl_datetime.md
│ │ ├── lbl_discrete.html
│ │ ├── lbl_discrete.md
│ │ ├── lbl_endpoint.html
│ │ ├── lbl_endpoints.html
│ │ ├── lbl_endpoints.md
│ │ ├── lbl_format.html
│ │ ├── lbl_format.md
│ │ ├── lbl_glue.html
│ │ ├── lbl_glue.md
│ │ ├── lbl_intervals.html
│ │ ├── lbl_intervals.md
│ │ ├── lbl_manual.html
│ │ ├── lbl_manual.md
│ │ ├── lbl_midpoints.html
│ │ ├── lbl_midpoints.md
│ │ ├── lbl_seq.html
│ │ ├── lbl_seq.md
│ │ ├── non-standard-types.html
│ │ ├── non-standard-types.md
│ │ ├── percent.html
│ │ ├── percent.md
│ │ ├── print.breaks.html
│ │ ├── santoku-cast.html
│ │ ├── santoku-cast.md
│ │ ├── santoku-package.html
│ │ ├── santoku-package.md
│ │ ├── santoku.html
│ │ ├── santoku_cast_common.Date.html
│ │ ├── santoku_cast_common.POSIXct.html
│ │ ├── santoku_cast_common.default.html
│ │ ├── santoku_cast_common.double.html
│ │ ├── santoku_cast_common.hexmode.html
│ │ ├── santoku_cast_common.integer64.html
│ │ ├── santoku_cast_common.octmode.html
│ │ ├── santoku_cast_common.ts.html
│ │ ├── santoku_cast_common.zoo.html
│ │ ├── sequence-labels.html
│ │ ├── tab.html
│ │ ├── tab_deciles.html
│ │ ├── tab_dissect.html
│ │ ├── tab_equally.html
│ │ ├── tab_evenly.html
│ │ ├── tab_fn.html
│ │ ├── tab_mean_sd.html
│ │ ├── tab_n.html
│ │ ├── tab_pretty.html
│ │ ├── tab_proportions.html
│ │ ├── tab_quantiles.html
│ │ ├── tab_spikes.html
│ │ └── tab_width.html
│ ├── search.json
│ ├── sitemap.xml
│ └── tutorials/
│ ├── 00-visualintroduction.html
│ ├── 00-visualintroduction.md
│ ├── 01-chopping-dates.html
│ ├── 01-chopping-dates.md
│ ├── index.html
│ └── index.md
├── man/
│ ├── breaks-class.Rd
│ ├── brk_default.Rd
│ ├── brk_manual.Rd
│ ├── brk_width-for-datetime.Rd
│ ├── chop.Rd
│ ├── chop_equally.Rd
│ ├── chop_evenly.Rd
│ ├── chop_fn.Rd
│ ├── chop_mean_sd.Rd
│ ├── chop_n.Rd
│ ├── chop_pretty.Rd
│ ├── chop_proportions.Rd
│ ├── chop_quantiles.Rd
│ ├── chop_spikes.Rd
│ ├── chop_width.Rd
│ ├── dissect.Rd
│ ├── exactly.Rd
│ ├── fillet.Rd
│ ├── lbl_dash.Rd
│ ├── lbl_datetime.Rd
│ ├── lbl_discrete.Rd
│ ├── lbl_endpoints.Rd
│ ├── lbl_glue.Rd
│ ├── lbl_intervals.Rd
│ ├── lbl_manual.Rd
│ ├── lbl_midpoints.Rd
│ ├── lbl_seq.Rd
│ ├── non-standard-types.Rd
│ ├── percent.Rd
│ ├── santoku-cast.Rd
│ └── santoku-package.Rd
├── pkgdown/
│ └── extra.css
├── release-process.R
├── santoku.Rproj
├── src/
│ ├── .gitignore
│ ├── RcppExports.cpp
│ └── categorize.cpp
├── tests/
│ ├── testthat/
│ │ ├── test-Date-DateTime.R
│ │ ├── test-breaks.R
│ │ ├── test-categorize.R
│ │ ├── test-chop.R
│ │ ├── test-labels.R
│ │ ├── test-nonstandard.R
│ │ ├── test-tab.R
│ │ └── test-zzz-systematic.R
│ └── testthat.R
└── vignettes/
├── .gitignore
├── santoku.Rmd
├── tutorials/
│ ├── chopping-dates-with-santoku.Rmd
│ ├── libs/
│ │ ├── Proj4Leaflet/
│ │ │ ├── proj4-compressed.js
│ │ │ └── proj4leaflet.js
│ │ ├── crosstalk/
│ │ │ ├── css/
│ │ │ │ └── crosstalk.css
│ │ │ └── js/
│ │ │ └── crosstalk.js
│ │ ├── datatables-binding/
│ │ │ └── datatables.js
│ │ ├── datatables-css/
│ │ │ └── datatables-crosstalk.css
│ │ ├── dt-core/
│ │ │ └── css/
│ │ │ └── jquery.dataTables.extra.css
│ │ ├── header-attrs/
│ │ │ └── header-attrs.js
│ │ ├── htmlwidgets/
│ │ │ └── htmlwidgets.js
│ │ ├── leaflet/
│ │ │ ├── leaflet.css
│ │ │ └── leaflet.js
│ │ ├── leaflet-binding/
│ │ │ └── leaflet.js
│ │ ├── leafletfix/
│ │ │ └── leafletfix.css
│ │ ├── remark-css/
│ │ │ ├── default-fonts.css
│ │ │ └── default.css
│ │ └── rstudio_leaflet/
│ │ └── rstudio_leaflet.css
│ ├── rsconnect/
│ │ └── documents/
│ │ └── visual-introduction.Rmd/
│ │ └── rpubs.com/
│ │ └── rpubs/
│ │ ├── Document.dcf
│ │ └── Publish Document.dcf
│ ├── visual-intro-styles.css
│ └── visual-introduction.Rmd
├── website-articles/
│ ├── performance.Rmd
│ └── performance_cache/
│ └── html/
│ ├── __packages
│ ├── unnamed-chunk-1_55ef34d700344288a08acda554dff8ac.RData
│ ├── unnamed-chunk-1_55ef34d700344288a08acda554dff8ac.rdb
│ └── unnamed-chunk-1_55ef34d700344288a08acda554dff8ac.rdx
└── whats-new-in-0-9-0.Rmd
SYMBOL INDEX (361 symbols across 12 files)
FILE: docs/deps/jquery-3.6.0/jquery-3.6.0.js
function DOMEval (line 107) | function DOMEval( code, node, doc ) {
function toType (line 137) | function toType( obj ) {
function isArrayLike (line 507) | function isArrayLike( obj ) {
function Sizzle (line 759) | function Sizzle( selector, context, results, seed ) {
function createCache (line 907) | function createCache() {
function markFunction (line 927) | function markFunction( fn ) {
function assert (line 936) | function assert( fn ) {
function addHandle (line 960) | function addHandle( attrs, handler ) {
function siblingCheck (line 975) | function siblingCheck( a, b ) {
function createInputPseudo (line 1001) | function createInputPseudo( type ) {
function createButtonPseudo (line 1012) | function createButtonPseudo( type ) {
function createDisabledPseudo (line 1023) | function createDisabledPseudo( disabled ) {
function createPositionalPseudo (line 1079) | function createPositionalPseudo( fn ) {
function testContext (line 1102) | function testContext( context ) {
function setFilters (line 2313) | function setFilters() {}
function toSelector (line 2387) | function toSelector( tokens ) {
function addCombinator (line 2397) | function addCombinator( matcher, combinator, base ) {
function elementMatcher (line 2464) | function elementMatcher( matchers ) {
function multipleContexts (line 2478) | function multipleContexts( selector, contexts, results ) {
function condense (line 2487) | function condense( unmatched, map, filter, context, xml ) {
function setMatcher (line 2508) | function setMatcher( preFilter, selector, matcher, postFilter, postFinde...
function matcherFromTokens (line 2608) | function matcherFromTokens( tokens ) {
function matcherFromGroupMatchers (line 2671) | function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
function nodeName (line 3029) | function nodeName( elem, name ) {
function winnow (line 3039) | function winnow( elements, qualifier, not ) {
function sibling (line 3334) | function sibling( cur, dir ) {
function createOptions (line 3427) | function createOptions( options ) {
function Identity (line 3652) | function Identity( v ) {
function Thrower (line 3655) | function Thrower( ex ) {
function adoptValue (line 3659) | function adoptValue( value, resolve, reject, noValue ) {
function resolve (line 3752) | function resolve( depth, deferred, handler, special ) {
function completed (line 4117) | function completed() {
function fcamelCase (line 4212) | function fcamelCase( _all, letter ) {
function camelCase (line 4219) | function camelCase( string ) {
function Data (line 4236) | function Data() {
function getData (line 4405) | function getData( data ) {
function dataAttr (line 4430) | function dataAttr( elem, key, data ) {
function adjustCSS (line 4742) | function adjustCSS( elem, prop, valueParts, tween ) {
function getDefaultDisplay (line 4810) | function getDefaultDisplay( elem ) {
function showHide (line 4833) | function showHide( elements, show ) {
function getAll (line 4965) | function getAll( context, tag ) {
function setGlobalEval (line 4990) | function setGlobalEval( elems, refElements ) {
function buildFragment (line 5006) | function buildFragment( elems, context, scripts, selection, ignored ) {
function returnTrue (line 5098) | function returnTrue() {
function returnFalse (line 5102) | function returnFalse() {
function expectSync (line 5112) | function expectSync( elem, type ) {
function safeActiveElement (line 5119) | function safeActiveElement() {
function on (line 5125) | function on( elem, types, selector, data, fn, one ) {
function leverageNative (line 5613) | function leverageNative( el, type, expectSync ) {
function manipulationTarget (line 5962) | function manipulationTarget( elem, content ) {
function disableScript (line 5973) | function disableScript( elem ) {
function restoreScript (line 5977) | function restoreScript( elem ) {
function cloneCopyEvent (line 5987) | function cloneCopyEvent( src, dest ) {
function fixInput (line 6020) | function fixInput( src, dest ) {
function domManip (line 6033) | function domManip( collection, args, callback, ignored ) {
function remove (line 6125) | function remove( elem, selector, keepData ) {
function computeStyleTests (line 6439) | function computeStyleTests() {
function roundPixelMeasures (line 6483) | function roundPixelMeasures( measure ) {
function curCSS (line 6576) | function curCSS( elem, name, computed ) {
function addGetHookIf (line 6629) | function addGetHookIf( conditionFn, hookFn ) {
function vendorPropName (line 6654) | function vendorPropName( name ) {
function finalPropName (line 6669) | function finalPropName( name ) {
function setPositiveNumber (line 6695) | function setPositiveNumber( _elem, value, subtract ) {
function boxModelAdjustment (line 6707) | function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, ...
function getWidthOrHeight (line 6775) | function getWidthOrHeight( elem, dimension, extra ) {
function Tween (line 7151) | function Tween( elem, options, prop, end, easing ) {
function schedule (line 7274) | function schedule() {
function createFxNow (line 7287) | function createFxNow() {
function genFx (line 7295) | function genFx( type, includeWidth ) {
function createTween (line 7315) | function createTween( value, prop, animation ) {
function defaultPrefilter (line 7329) | function defaultPrefilter( elem, props, opts ) {
function propFilter (line 7501) | function propFilter( props, specialEasing ) {
function Animation (line 7538) | function Animation( elem, properties, options ) {
function stripAndCollapse (line 8254) | function stripAndCollapse( value ) {
function getClass (line 8260) | function getClass( elem ) {
function classesToArray (line 8264) | function classesToArray( value ) {
function buildParams (line 8894) | function buildParams( prefix, obj, traditional, add ) {
function addToPrefiltersOrTransports (line 9047) | function addToPrefiltersOrTransports( structure ) {
function inspectPrefiltersOrTransports (line 9081) | function inspectPrefiltersOrTransports( structure, options, originalOpti...
function ajaxExtend (line 9110) | function ajaxExtend( target, src ) {
function ajaxHandleResponses (line 9130) | function ajaxHandleResponses( s, jqXHR, responses ) {
function ajaxConvert (line 9188) | function ajaxConvert( s, response, jqXHR, isSuccess ) {
function done (line 9704) | function done( status, nativeStatusText, responses, headers ) {
FILE: docs/docsearch.js
function matchedWords (line 54) | function matchedWords(hit) {
function updateHitURL (line 73) | function updateHitURL(hit) {
FILE: docs/lightswitch.js
function bsSetupThemeToggle (line 29) | function bsSetupThemeToggle() {
FILE: docs/pkgdown.js
function changeTooltipMessage (line 32) | function changeTooltipMessage(element, msg) {
function searchFuse (line 116) | async function searchFuse(query, callback) {
FILE: src/RcppExports.cpp
function RcppExport (line 15) | RcppExport SEXP _santoku_categorize_impl(SEXP xSEXP, SEXP breaksSEXP, SE...
function RcppExport (line 32) | RcppExport void R_init_santoku(DllInfo *dll) {
FILE: src/categorize.cpp
function IntegerVector (line 5) | IntegerVector categorize_impl(NumericVector x, NumericVector breaks, Log...
FILE: vignettes/tutorials/libs/Proj4Leaflet/proj4-compressed.js
function e (line 1) | function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&re...
function Point (line 1) | function Point(a,b,c){if(!(this instanceof Point))return new Point(a,b,c...
function Projection (line 1) | function Projection(a,b){if(!(this instanceof Projection))return new Pro...
function d (line 1) | function d(a,b,c){var d;return Array.isArray(c)?(d=g(a,b,c),3===c.length...
function e (line 1) | function e(a){return a instanceof f?a:a.oProj?a.oProj:f(a)}
function proj4 (line 1) | function proj4(a,b,c){a=e(a);var f,g=!1;return"undefined"==typeof b?(b=a...
function j (line 1) | function j(a){return a===d||a===e}
function d (line 1) | function d(a){var b=this;if(2===arguments.length){var c=arguments[1];"st...
function d (line 1) | function d(a){return"string"==typeof a}
function e (line 1) | function e(a){return a in i}
function f (line 1) | function f(a){var b=["GEOGCS","GEOCCS","PROJCS","LOCAL_CS"];return b.red...
function g (line 1) | function g(a){return"+"===a[0]}
function h (line 1) | function h(a){return d(a)?e(a)?i[a]:f(a)?j(a):g(a)?k(a):void 0:a}
function d (line 1) | function d(a,b){var c=g.length;return a.names?(g[c]=a,a.names.forEach(fu...
function d (line 2) | function d(a){return a}
function m (line 2) | function m(a,b){return(a.datum.datum_type===f||a.datum.datum_type===g)&&...
function d (line 2) | function d(a,b,c){a[b]=c.map(function(a){var b={};return e(a,b),b}).redu...
function e (line 2) | function e(a,b){var c;return Array.isArray(a)?(c=a.shift(),"PARAMETER"==...
function f (line 2) | function f(a,b){var c=b[0],d=b[1];!(c in a)&&d in a&&(a[c]=a[d],3===b.le...
function g (line 2) | function g(a){return a*i}
function h (line 2) | function h(a){function b(b){var c=a.to_meter||1;return parseFloat(b,10)*...
function d (line 3) | function d(a){return a*(Math.PI/180)}
function e (line 3) | function e(a){return 180*(a/Math.PI)}
function f (line 3) | function f(a){var b,c,e,f,g,i,j,k,l,m=a.lat,n=a.lon,o=6378137,p=.0066943...
function g (line 3) | function g(a){var b=a.northing,c=a.easting,d=a.zoneLetter,f=a.zoneNumber...
function h (line 3) | function h(a){var b="Z";return 84>=a&&a>=72?b="X":72>a&&a>=64?b="W":64>a...
function i (line 3) | function i(a,b){var c="00000"+a.easting,d="00000"+a.northing;return a.zo...
function j (line 3) | function j(a,b,c){var d=k(c),e=Math.floor(a/1e5),f=Math.floor(b/1e5)%20;...
function k (line 3) | function k(a){var b=a%q;return 0===b&&(b=q),b}
function l (line 3) | function l(a,b,c){var d=c-1,e=r.charCodeAt(d),f=s.charCodeAt(d),g=e+a-1,...
function m (line 3) | function m(a){if(a&&0===a.length)throw"MGRSPoint coverting from nothing"...
function n (line 3) | function n(a,b){for(var c=r.charCodeAt(b-1),d=1e5,e=!1;c!==a.charCodeAt(...
function o (line 3) | function o(a,b){if(a>"V")throw"MGRSPoint given invalid Northing "+a;for(...
function p (line 3) | function p(a){var b;switch(a){case"C":b=11e5;break;case"D":b=2e6;break;c...
FILE: vignettes/tutorials/libs/crosstalk/js/crosstalk.js
function e (line 1) | function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof requi...
function defineProperties (line 8) | function defineProperties(target, props) { for (var i = 0; i < props.len...
function _classCallCheck (line 10) | function _classCallCheck(instance, Constructor) { if (!(instance instanc...
function Events (line 13) | function Events() {
function defineProperties (line 83) | function defineProperties(target, props) { for (var i = 0; i < props.len...
function _interopRequireWildcard (line 101) | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { ret...
function _interopRequireDefault (line 103) | function _interopRequireDefault(obj) { return obj && obj.__esModule ? ob...
function _classCallCheck (line 105) | function _classCallCheck(instance, Constructor) { if (!(instance instanc...
function getFilterSet (line 107) | function getFilterSet(group) {
function nextId (line 118) | function nextId() {
function FilterHandle (line 148) | function FilterHandle(group, extraInfo) {
function defineProperties (line 363) | function defineProperties(target, props) { for (var i = 0; i < props.len...
function _classCallCheck (line 367) | function _classCallCheck(instance, Constructor) { if (!(instance instanc...
function naturalComparator (line 369) | function naturalComparator(a, b) {
function FilterSet (line 384) | function FilterSet() {
function defineProperties (line 494) | function defineProperties(target, props) { for (var i = 0; i < props.len...
function _interopRequireDefault (line 504) | function _interopRequireDefault(obj) { return obj && obj.__esModule ? ob...
function _classCallCheck (line 506) | function _classCallCheck(instance, Constructor) { if (!(instance instanc...
function group (line 513) | function group(groupName) {
function Group (line 530) | function Group(name) {
function _interopRequireDefault (line 587) | function _interopRequireDefault(obj) { return obj && obj.__esModule ? ob...
function var_ (line 591) | function var_(name) {
function has (line 595) | function has(name) {
function register (line 640) | function register(reg) {
function bind (line 651) | function bind() {
function $escape (line 661) | function $escape(val) {
function bindEl (line 665) | function bindEl(el) {
function bindInstance (line 675) | function bindInstance(binding, el) {
function _interopRequireWildcard (line 724) | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { ret...
function _interopRequireWildcard (line 786) | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { ret...
function sliceIterator (line 851) | function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = ...
function _interopRequireWildcard (line 859) | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { ret...
function getValue (line 903) | function getValue() {
function padZeros (line 983) | function padZeros(n, digits) {
function formatDateUTC (line 992) | function formatDateUTC(date) {
function defineProperties (line 1010) | function defineProperties(target, props) { for (var i = 0; i < props.len...
function _interopRequireWildcard (line 1024) | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { ret...
function _interopRequireDefault (line 1026) | function _interopRequireDefault(obj) { return obj && obj.__esModule ? ob...
function _classCallCheck (line 1028) | function _classCallCheck(instance, Constructor) { if (!(instance instanc...
function SelectionHandle (line 1047) | function SelectionHandle() {
function defineProperties (line 1251) | function defineProperties(target, props) { for (var i = 0; i < props.len...
function _classCallCheck (line 1260) | function _classCallCheck(instance, Constructor) { if (!(instance instanc...
function extend (line 1262) | function extend(target) {
function checkSorted (line 1280) | function checkSorted(list) {
function diffSortedLists (line 1288) | function diffSortedLists(a, b) {
function dataframeToD3 (line 1322) | function dataframeToD3(df) {
function SubscriptionTracker (line 1354) | function SubscriptionTracker(emitter) {
function defineProperties (line 1403) | function defineProperties(target, props) { for (var i = 0; i < props.len...
function _interopRequireDefault (line 1409) | function _interopRequireDefault(obj) { return obj && obj.__esModule ? ob...
function _classCallCheck (line 1411) | function _classCallCheck(instance, Constructor) { if (!(instance instanc...
function Var (line 1414) | function Var(group, name, /*optional*/value) {
FILE: vignettes/tutorials/libs/datatables-binding/datatables.js
function maybeInstallCrosstalkPlugins (line 76) | function maybeInstallCrosstalkPlugins() {
function keysToMatches (line 301) | function keysToMatches(keys) {
function applyCrosstalkFilter (line 318) | function applyCrosstalkFilter(e) {
function applyCrosstalkSelection (line 325) | function applyCrosstalkSelection(e) {
FILE: vignettes/tutorials/libs/htmlwidgets/htmlwidgets.js
function querySelectorAll (line 19) | function querySelectorAll(scope, selector) {
function asArray (line 28) | function asArray(value) {
function extend (line 37) | function extend(target /*, ... */) {
function forEach (line 53) | function forEach(values, callback, thisArg) {
function overrideMethod (line 70) | function overrideMethod(target, methodName, funcSource) {
function delegateMethod (line 98) | function delegateMethod(delegator, delegatee, methodName) {
function elementData (line 118) | function elementData(el, name, value) {
function escapeRegExp (line 131) | function escapeRegExp(str) {
function hasClass (line 135) | function hasClass(el, className) {
function filterByClass (line 144) | function filterByClass(elements, className, include) {
function on (line 153) | function on(obj, eventName, func) {
function off (line 161) | function off(obj, eventName, func) {
function unpackPadding (line 172) | function unpackPadding(value) {
function paddingToCss (line 190) | function paddingToCss(paddingObj) {
function px (line 195) | function px(x) {
function sizingPolicy (line 205) | function sizingPolicy(el) {
function evalAndRun (line 228) | function evalAndRun(tasks, target, args) {
function tryEval (line 249) | function tryEval(code) {
function initSizing (line 270) | function initSizing(el) {
function scheduleStaticRender (line 561) | function scheduleStaticRender() {
function has_jQuery3 (line 663) | function has_jQuery3() {
function maybeStaticRenderLater (line 694) | function maybeStaticRenderLater() {
function splitWithEscape (line 766) | function splitWithEscape(value, splitChar, escapeChar) {
function invokePostRenderHandlers (line 859) | function invokePostRenderHandlers() {
function createLegacyDefinitionAdapter (line 878) | function createLegacyDefinitionAdapter(defn) {
FILE: vignettes/tutorials/libs/leaflet-binding/leaflet.js
function r (line 1) | function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==...
function defineProperties (line 8) | function defineProperties(target, props) { for (var i = 0; i < props.len...
function _classCallCheck (line 12) | function _classCallCheck(instance, Constructor) { if (!(instance instanc...
function ClusterLayerStore (line 15) | function ClusterLayerStore(group) {
function defineProperties (line 69) | function defineProperties(target, props) { for (var i = 0; i < props.len...
function _classCallCheck (line 71) | function _classCallCheck(instance, Constructor) { if (!(instance instanc...
function ControlStore (line 74) | function ControlStore(map) {
function _interopRequireDefault (line 152) | function _interopRequireDefault(obj) { return obj && obj.__esModule ? ob...
function getCRS (line 155) | function getCRS(crsOptions) {
function defineProperties (line 203) | function defineProperties(target, props) { for (var i = 0; i < props.len...
function _classCallCheck (line 207) | function _classCallCheck(instance, Constructor) { if (!(instance instanc...
function DataFrame (line 210) | function DataFrame() {
function _interopRequireDefault (line 321) | function _interopRequireDefault(obj) { return obj && obj.__esModule ? ob...
function _interopRequireDefault (line 359) | function _interopRequireDefault(obj) { return obj && obj.__esModule ? ob...
function _interopRequireDefault (line 392) | function _interopRequireDefault(obj) { return obj && obj.__esModule ? ob...
function _interopRequireDefault (line 523) | function _interopRequireDefault(obj) { return obj && obj.__esModule ? ob...
function updateBounds (line 533) | function updateBounds(map) {
function preventUnintendedZoomOnScroll (line 550) | function preventUnintendedZoomOnScroll(map) {
function needsZoom (line 733) | function needsZoom() {
function defineProperties (line 810) | function defineProperties(target, props) { for (var i = 0; i < props.len...
function _interopRequireDefault (line 822) | function _interopRequireDefault(obj) { return obj && obj.__esModule ? ob...
function _classCallCheck (line 824) | function _classCallCheck(instance, Constructor) { if (!(instance instanc...
function LayerManager (line 827) | function LayerManager(map) {
function clearLayerGroup (line 1158) | function clearLayerGroup(key, layerGroup) {
function _interopRequireDefault (line 1263) | function _interopRequireDefault(obj) { return obj && obj.__esModule ? ob...
function mouseHandler (line 1269) | function mouseHandler(mapId, layerId, group, eventName, extraInfo) {
function unpackStrings (line 1372) | function unpackStrings(iconset) {
function addMarkers (line 1388) | function addMarkers(map, df, group, clusterOptions, clusterId, markerFun...
function addLayers (line 1554) | function addLayers(map, category, df, layerFunc) {
function onAdd (line 1820) | function onAdd(map) {
function onRemove (line 1843) | function onRemove(map) {
function setupShowHideGroupsOnZoom (line 2081) | function setupShowHideGroupsOnZoom(map) {
function degree2tile (line 2150) | function degree2tile(lat, lng, zoom) {
function overlap (line 2162) | function overlap(from, to, x, /* optional */x1) {
function getCanvasSmoothingProperty (line 2167) | function getCanvasSmoothingProperty(ctx) {
function getImageData (line 2205) | function getImageData(callback) {
function defineProperties (line 2501) | function defineProperties(target, props) { for (var i = 0; i < props.len...
function _classCallCheck (line 2503) | function _classCallCheck(instance, Constructor) { if (!(instance instanc...
function Mipmapper (line 2511) | function Mipmapper(img) {
function log (line 2609) | function log(message) {
function recycle (line 2615) | function recycle(values, length, inPlace) {
function asArray (line 2637) | function asArray(value) {
FILE: vignettes/tutorials/libs/leaflet/leaflet.js
function i (line 5) | function i(t){var i,e,n,o;for(e=1,n=arguments.length;e<n;e++){o=argument...
function e (line 5) | function e(t,i){var e=Array.prototype.slice;if(t.bind)return t.bind.appl...
function n (line 5) | function n(t){return t._leaflet_id=t._leaflet_id||++ti,t._leaflet_id}
function o (line 5) | function o(t,i,e){var n,o,s,r;return r=function(){n=!1,o&&(s.apply(e,o),...
function s (line 5) | function s(t,i,e){var n=i[1],o=i[0],s=n-o;return t===n&&e?t:((t-o)%s+s)%...
function r (line 5) | function r(){return!1}
function a (line 5) | function a(t,i){var e=Math.pow(10,void 0===i?6:i);return Math.round(t*e)/e}
function h (line 5) | function h(t){return t.trim?t.trim():t.replace(/^\s+|\s+$/g,"")}
function u (line 5) | function u(t){return h(t).split(/\s+/)}
function l (line 5) | function l(t,i){t.hasOwnProperty("options")||(t.options=t.options?Qt(t.o...
function c (line 5) | function c(t,i,e){var n=[];for(var o in t)n.push(encodeURIComponent(e?o....
function _ (line 5) | function _(t,i){return t.replace(ii,function(t,e){var n=i[e];if(void 0==...
function d (line 5) | function d(t,i){for(var e=0;e<t.length;e++)if(t[e]===i)return e;return-1}
function p (line 5) | function p(t){return window["webkit"+t]||window["moz"+t]||window["ms"+t]}
function m (line 5) | function m(t){var i=+new Date,e=Math.max(0,16-(i-oi));return oi=i+e,wind...
function f (line 5) | function f(t,i,n){if(!n||si!==m)return si.call(window,e(t,i));t.call(i)}
function g (line 5) | function g(t){t&&ri.call(window,t)}
function v (line 5) | function v(){}
function y (line 5) | function y(t){if("undefined"!=typeof L&&L&&L.Mixin){t=ei(t)?t:[t];for(va...
function x (line 5) | function x(t,i,e){this.x=e?Math.round(t):t,this.y=e?Math.round(i):i}
function w (line 5) | function w(t,i,e){return t instanceof x?t:ei(t)?new x(t[0],t[1]):void 0=...
function P (line 5) | function P(t,i){if(t)for(var e=i?[t,i]:t,n=0,o=e.length;n<o;n++)this.ext...
function b (line 5) | function b(t,i){return!t||t instanceof P?t:new P(t,i)}
function T (line 5) | function T(t,i){if(t)for(var e=i?[t,i]:t,n=0,o=e.length;n<o;n++)this.ext...
function z (line 5) | function z(t,i){return t instanceof T?t:new T(t,i)}
function M (line 5) | function M(t,i,e){if(isNaN(t)||isNaN(i))throw new Error("Invalid LatLng ...
function C (line 5) | function C(t,i,e){return t instanceof M?t:ei(t)&&"object"!=typeof t[0]?3...
function Z (line 5) | function Z(t,i,e,n){if(ei(t))return this._a=t[0],this._b=t[1],this._c=t[...
function S (line 5) | function S(t,i,e,n){return new Z(t,i,e,n)}
function E (line 5) | function E(t){return document.createElementNS("http://www.w3.org/2000/sv...
function k (line 5) | function k(t,i){var e,n,o,s,r,a,h="";for(e=0,o=t.length;e<o;e++){for(n=0...
function A (line 5) | function A(t){return navigator.userAgent.toLowerCase().indexOf(t)>=0}
function I (line 5) | function I(t,i,e,n){return"touchstart"===i?O(t,e,n):"touchmove"===i?W(t,...
function B (line 5) | function B(t,i,e){var n=t["_leaflet_"+i+e];return"touchstart"===i?t.remo...
function O (line 5) | function O(t,i,n){var o=e(function(t){if("mouse"!==t.pointerType&&t.MSPO...
function R (line 5) | function R(t){oe[t.pointerId]=t,re++}
function D (line 5) | function D(t){oe[t.pointerId]&&(oe[t.pointerId]=t)}
function N (line 5) | function N(t){delete oe[t.pointerId],re--}
function j (line 5) | function j(t,i){t.touches=[];for(var e in oe)t.touches.push(oe[e]);t.cha...
function W (line 5) | function W(t,i,e){var n=function(t){(t.pointerType!==t.MSPOINTER_TYPE_MO...
function H (line 5) | function H(t,i,e){var n=function(t){j(t,i)};t["_leaflet_touchend"+e]=n,t...
function F (line 5) | function F(t,i,e){function n(t){var i;if(Ui){if(!Pi||"mouse"===t.pointer...
function U (line 5) | function U(t,i){var e=t[ue+ae+i],n=t[ue+he+i],o=t[ue+"dblclick"+i];retur...
function V (line 5) | function V(t,i,e,n){if("object"==typeof i)for(var o in i)G(t,o,i[o],e);e...
function q (line 5) | function q(t,i,e,n){if("object"==typeof i)for(var o in i)K(t,o,i[o],e);e...
function G (line 5) | function G(t,i,e,o){var s=i+n(e)+(o?"_"+n(o):"");if(t[le]&&t[le][s])retu...
function K (line 5) | function K(t,i,e,o){var s=i+n(e)+(o?"_"+n(o):""),r=t[le]&&t[le][s];if(!r...
function Y (line 5) | function Y(t){return t.stopPropagation?t.stopPropagation():t.originalEve...
function X (line 5) | function X(t){return G(t,"mousewheel",Y),this}
function J (line 5) | function J(t){return V(t,"mousedown touchstart dblclick",Y),G(t,"click",...
function $ (line 5) | function $(t){return t.preventDefault?t.preventDefault():t.returnValue=!...
function Q (line 5) | function Q(t){return $(t),Y(t),this}
function tt (line 5) | function tt(t,i){if(!i)return new x(t.clientX,t.clientY);var e=i.getBoun...
function it (line 5) | function it(t){return Pi?t.wheelDeltaY/2:t.deltaY&&0===t.deltaMode?-t.de...
function et (line 5) | function et(t){_e[t.type]=!0}
function nt (line 5) | function nt(t){var i=_e[t.type];return _e[t.type]=!1,i}
function ot (line 5) | function ot(t,i){var e=i.relatedTarget;if(!e)return!0;try{for(;e&&e!==t;...
function st (line 5) | function st(t,i){var e=t.timeStamp||t.originalEvent&&t.originalEvent.tim...
function rt (line 5) | function rt(t){return"string"==typeof t?document.getElementById(t):t}
function at (line 5) | function at(t,i){var e=t.style[i]||t.currentStyle&&t.currentStyle[i];if(...
function ht (line 5) | function ht(t,i,e){var n=document.createElement(t);return n.className=i|...
function ut (line 5) | function ut(t){var i=t.parentNode;i&&i.removeChild(t)}
function lt (line 5) | function lt(t){for(;t.firstChild;)t.removeChild(t.firstChild)}
function ct (line 5) | function ct(t){var i=t.parentNode;i.lastChild!==t&&i.appendChild(t)}
function _t (line 5) | function _t(t){var i=t.parentNode;i.firstChild!==t&&i.insertBefore(t,i.f...
function dt (line 5) | function dt(t,i){if(void 0!==t.classList)return t.classList.contains(i);...
function pt (line 5) | function pt(t,i){if(void 0!==t.classList)for(var e=u(i),n=0,o=e.length;n...
function mt (line 5) | function mt(t,i){void 0!==t.classList?t.classList.remove(i):ft(t,h((" "+...
function ft (line 5) | function ft(t,i){void 0===t.className.baseVal?t.className=i:t.className....
function gt (line 5) | function gt(t){return void 0===t.className.baseVal?t.className:t.classNa...
function vt (line 5) | function vt(t,i){"opacity"in t.style?t.style.opacity=i:"filter"in t.styl...
function yt (line 5) | function yt(t,i){var e=!1,n="DXImageTransform.Microsoft.Alpha";try{e=t.f...
function xt (line 5) | function xt(t){for(var i=document.documentElement.style,e=0;e<t.length;e...
function wt (line 5) | function wt(t,i,e){var n=i||new x(0,0);t.style[pe]=(Oi?"translate("+n.x+...
function Lt (line 5) | function Lt(t,i){t._leaflet_pos=i,Ni?wt(t,i):(t.style.left=i.x+"px",t.st...
function Pt (line 5) | function Pt(t){return t._leaflet_pos||new x(0,0)}
function bt (line 5) | function bt(){V(window,"dragstart",$)}
function Tt (line 5) | function Tt(){q(window,"dragstart",$)}
function zt (line 5) | function zt(t){for(;-1===t.tabIndex;)t=t.parentNode;t.style&&(Mt(),ve=t,...
function Mt (line 5) | function Mt(){ve&&(ve.style.outline=ye,ve=void 0,ye=void 0,q(window,"key...
function Ct (line 5) | function Ct(t,i){if(!i||!t.length)return t.slice();var e=i*i;return t=kt...
function Zt (line 5) | function Zt(t,i,e){return Math.sqrt(Rt(t,i,e,!0))}
function St (line 5) | function St(t,i){var e=t.length,n=new(typeof Uint8Array!=void 0+""?Uint8...
function Et (line 5) | function Et(t,i,e,n,o){var s,r,a,h=0;for(r=n+1;r<=o-1;r++)(a=Rt(t[r],t[n...
function kt (line 5) | function kt(t,i){for(var e=[t[0]],n=1,o=0,s=t.length;n<s;n++)Ot(t[n],t[o...
function At (line 5) | function At(t,i,e,n,o){var s,r,a,h=n?Se:Bt(t,e),u=Bt(i,e);for(Se=u;;){if...
function It (line 5) | function It(t,i,e,n,o){var s,r,a=i.x-t.x,h=i.y-t.y,u=n.min,l=n.max;retur...
function Bt (line 5) | function Bt(t,i){var e=0;return t.x<i.min.x?e|=1:t.x>i.max.x&&(e|=2),t.y...
function Ot (line 5) | function Ot(t,i){var e=i.x-t.x,n=i.y-t.y;return e*e+n*n}
function Rt (line 5) | function Rt(t,i,e,n){var o,s=i.x,r=i.y,a=e.x-s,h=e.y-r,u=a*a+h*h;return ...
function Dt (line 5) | function Dt(t){return!ei(t[0])||"object"!=typeof t[0][0]&&void 0!==t[0][0]}
function Nt (line 5) | function Nt(t){return console.warn("Deprecated use of _flat, please use ...
function jt (line 5) | function jt(t,i,e){var n,o,s,r,a,h,u,l,c,_=[1,4,2,8];for(o=0,u=t.length;...
function Wt (line 5) | function Wt(t,i){var e,n,o,s,r="Feature"===t.type?t.geometry:t,a=r?r.coo...
function Ht (line 5) | function Ht(t){return new M(t[1],t[0],t[2])}
function Ft (line 5) | function Ft(t,i,e){for(var n,o=[],s=0,r=t.length;s<r;s++)n=i?Ft(t[s],i-1...
function Ut (line 5) | function Ut(t,i){return i="number"==typeof i?i:6,void 0!==t.alt?[a(t.lng...
function Vt (line 5) | function Vt(t,i,e,n){for(var o=[],s=0,r=t.length;s<r;s++)o.push(i?Vt(t[s...
function qt (line 5) | function qt(t,e){return t.feature?i({},t.feature,{geometry:e}):Gt(e)}
function Gt (line 5) | function Gt(t){return"Feature"===t.type||"FeatureCollection"===t.type?t:...
function Kt (line 5) | function Kt(t,i){return new nn(t,i)}
function Yt (line 5) | function Yt(t,i){return new dn(t,i)}
function Xt (line 5) | function Xt(t){return Yi?new fn(t):null}
function Jt (line 5) | function Jt(t){return Xi||Ji?new xn(t):null}
function t (line 5) | function t(){}
function n (line 5) | function n(t){var i=(g*g-m*m+(t?-1:1)*x*x*v*v)/(2*(t?g:m)*x*v),e=Math.sq...
function o (line 5) | function o(t){return(Math.exp(t)-Math.exp(-t))/2}
function s (line 5) | function s(t){return(Math.exp(t)+Math.exp(-t))/2}
function r (line 5) | function r(t){return o(t)/s(t)}
function a (line 5) | function a(t){return m*(s(w)/s(w+y*t))}
function h (line 5) | function h(t){return m*(s(w)*r(w+y*t)-o(w))/x}
function u (line 5) | function u(t){return 1-Math.pow(1-t,1.5)}
function l (line 5) | function l(){var e=(Date.now()-L)/b,n=u(e)*P;e<=1?(this._flyToFrame=f(l,...
function t (line 5) | function t(t,o){var s=e+t+" "+e+o;i[t+o]=ht("div",s,n)}
Condensed preview — 287 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (2,482K chars).
[
{
"path": ".Rbuildignore",
"chars": 346,
"preview": "^.*\\.Rproj$\n^\\.Rproj\\.user$\n^LICENSE\\.md$\n^TODO\\.md$\n^README\\.Rmd$\n^AGENTS\\.md$\n^index\\.Rmd$\n^advantages\\.Rmd$\n^release-"
},
{
"path": ".github/.gitignore",
"chars": 7,
"preview": "*.html\n"
},
{
"path": ".github/workflows/R-CMD-check.yaml",
"chars": 1396,
"preview": "# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples\n# Need help debugging build failures? Start at"
},
{
"path": ".github/workflows/test-coverage.yaml",
"chars": 1813,
"preview": "# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples\n# Need help debugging build failures? Start at"
},
{
"path": ".gitignore",
"chars": 87,
"preview": ".Rproj.user\n.Rhistory\n.RData\n.Ruserdata\ninst/doc\ndoc\nMeta\n.DS_Store\nrevdep/**\n.positai\n"
},
{
"path": "AGENTS.md",
"chars": 2859,
"preview": "# AGENTS.md\n\nThis file provides guidance to agents when working with code in this repository.\n\n## Project Overview\n\nsant"
},
{
"path": "DESCRIPTION",
"chars": 1345,
"preview": "Package: santoku\nType: Package\nTitle: A Versatile Cutting Tool\nVersion: 1.2.1\nAuthors@R:\n c(\n person(given = \"Davi"
},
{
"path": "LICENSE",
"chars": 46,
"preview": "YEAR: 2020\nCOPYRIGHT HOLDER: David Hugh-Jones\n"
},
{
"path": "LICENSE.md",
"chars": 1075,
"preview": "# MIT License\n\nCopyright (c) 2019 David Hugh-Jones\n\nPermission is hereby granted, free of charge, to any person obtainin"
},
{
"path": "NAMESPACE",
"chars": 3807,
"preview": "# Generated by roxygen2: do not edit by hand\n\nS3method(apply_format,\"function\")\nS3method(apply_format,character)\nS3metho"
},
{
"path": "NEWS.md",
"chars": 11047,
"preview": "# santoku 1.2.1\n\n* Fixed a test bug.\n\n\n# santoku 1.2.0\n\n* New experimental `lbl_date()` and `lbl_datetime()` functions f"
},
{
"path": "R/RcppExports.R",
"chars": 232,
"preview": "# Generated by using Rcpp::compileAttributes() -> do not edit by hand\n# Generator token: 10BE3573-1514-4C36-9D1C-5A225CD"
},
{
"path": "R/breaks-by-group-size.R",
"chars": 5463,
"preview": "\n#' @rdname chop_quantiles\n#'\n#' @export\n#' @order 2\nbrk_quantiles <- function (probs, ..., weights = NULL, recalc_probs"
},
{
"path": "R/breaks-by-width.R",
"chars": 3321,
"preview": "\n#' Equal-width intervals for dates or datetimes\n#'\n#' `brk_width()` can be used with time interval classes from base R "
},
{
"path": "R/breaks-impl.R",
"chars": 10762,
"preview": "\n\n#' Create a breaks object\n#'\n#' @param obj A sorted vector or a `breaks` object.\n#' @param left A logical vector, same"
},
{
"path": "R/breaks-misc.R",
"chars": 4117,
"preview": "\n#' Create a `breaks` object manually\n#'\n#' @param breaks A vector, which must be sorted.\n#' @param left_vec A logical v"
},
{
"path": "R/breaks.R",
"chars": 3027,
"preview": "\n#' @param breaks A numeric vector.\n#' @name breaks-doc\n#' @return A function which returns an object of class `breaks`."
},
{
"path": "R/categorize.R",
"chars": 1924,
"preview": "\n#' Categorize `x` according to breaks\n#'\n#' @param x A vector of data\n#' @param breaks A breaks object\n#'\n#' @return A "
},
{
"path": "R/chop-by-group-size.R",
"chars": 5386,
"preview": "\n#' Chop by quantiles\n#'\n#' `chop_quantiles()` chops data by quantiles.\n#' `chop_deciles()` is a convenience function wh"
},
{
"path": "R/chop-by-width.R",
"chars": 2262,
"preview": "\n#' Chop into fixed-width intervals\n#'\n#' `chop_width()` chops `x` into intervals of fixed `width`.\n#'\n#' @param width W"
},
{
"path": "R/chop-isolates.R",
"chars": 5557,
"preview": "\n#' Chop common values into singleton intervals\n#'\n#' `chop_spikes()` lets you chop common values of `x` into their own\n"
},
{
"path": "R/chop-misc.R",
"chars": 2725,
"preview": "\n\n#' Chop by standard deviations\n#'\n#' Intervals are measured in standard deviations on either side of the\n#' mean.\n#'\n#"
},
{
"path": "R/chop.R",
"chars": 9190,
"preview": "\n\n#' @name chop-doc\n#' @param ... Passed to [chop()].\n#' @return\n#' `chop_*` functions return a [`factor`][base::factor]"
},
{
"path": "R/labels-datetime.R",
"chars": 7465,
"preview": "#' Parse a `strftime` format string\n#'\n#' Splits a format string into literal and directive tokens.\n#'\n#' @param fmt A `"
},
{
"path": "R/labels-glue.R",
"chars": 4424,
"preview": "\n#' Label chopped intervals using the `glue` package\n#'\n#' Use `\"{l}\"` and `\"{r}\"` to show the left and right endpoints "
},
{
"path": "R/labels-impl.R",
"chars": 8517,
"preview": "\n\n#' Replaces labels with names from the breaks vector\n#'\n#' Only non-zero-char names are used.\n#'\n#' @param labels Pass"
},
{
"path": "R/labels-single.R",
"chars": 6182,
"preview": "\n#' Label chopped intervals by their midpoints\n#'\n#' This uses the midpoint of each interval for\n#' its label.\n#'\n#' @in"
},
{
"path": "R/labels.R",
"chars": 7183,
"preview": "\n#' @name label-doc\n#' @param fmt String, list or function. A format for break endpoints.\n#' @param raw `r lifecycle::ba"
},
{
"path": "R/non-standard-types-doc.R",
"chars": 908,
"preview": "\n#' Tips for chopping non-standard types\n#'\n#' Santoku can handle many non-standard types.\n#'\n#' * If objects can be com"
},
{
"path": "R/santoku-cast.R",
"chars": 6316,
"preview": "\n\n#' Hacked version of [vctrs::vec_cast_common()]\n#'\n#' @param x,y Vectors to cast\n#'\n#' @return A list of two vectors o"
},
{
"path": "R/santoku-package.R",
"chars": 1976,
"preview": "\n#' @import assertthat\nNULL\n\n#' A versatile cutting tool for R: package overview and options\n#'\n#' santoku is a tool for"
},
{
"path": "R/tab.R",
"chars": 4176,
"preview": "\n\n#' @rdname chop\n#' @export\n#' @examples\n#' tab(1:10, c(2, 5, 8))\n#'\ntab <- function (\n x,\n breaks,\n "
},
{
"path": "R/utils.R",
"chars": 2963,
"preview": "\n\n#' Define singleton intervals explicitly\n#'\n#' `exactly()` duplicates its input.\n#' It lets you define singleton inter"
},
{
"path": "README.Rmd",
"chars": 2473,
"preview": "---\noutput: github_document\n---\n\n<!-- README.md is generated from README.Rmd. Please edit that file -->\n\n```{r, include "
},
{
"path": "README.md",
"chars": 4182,
"preview": "\n<!-- README.md is generated from README.Rmd. Please edit that file -->\n\n# santoku <img src=\"man/figures/logo.png\" align"
},
{
"path": "TODO.md",
"chars": 5629,
"preview": "\n\n# TODO\n\n* Work on tests\n - tests for `left` and `close_end` arguments\n - tests for `brk_default`\n - `brk_width()` n"
},
{
"path": "_pkgdown.yml",
"chars": 1693,
"preview": "destination: docs\n\nurl: https://hughjonesd.github.io/santoku\ntemplate:\n bootstrap: 5\n bootswatch: darkly\n theme: arro"
},
{
"path": "advantages.Rmd",
"chars": 1083,
"preview": "\n## Advantages\n\n Here are some advantages of santoku:\n\n * By default, `chop()` always covers the whole range of the data"
},
{
"path": "codecov.yml",
"chars": 176,
"preview": "comment: false\n\ncoverage:\n status:\n project:\n default:\n target: auto\n threshold: 1%\n patch:\n "
},
{
"path": "cran-comments.md",
"chars": 291,
"preview": "\nVersion 1.2.1. Fixes (I hope) a testing bug exposed on CRAN.\n\n## Test environments\n\n* local (macOS), R 4.6.0\n* github ("
},
{
"path": "docs/404.html",
"chars": 5942,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\">\n<head>\n<meta http-equiv=\"Content-Type"
},
{
"path": "docs/404.md",
"chars": 75,
"preview": "Content not found. Please use links in the navbar.\n\n# Page not found (404)\n"
},
{
"path": "docs/AGENTS.html",
"chars": 12750,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\"><head><meta http-equiv=\"Content-Type\" "
},
{
"path": "docs/AGENTS.md",
"chars": 3248,
"preview": "# AGENTS.md\n\nThis file provides guidance to agents when working with code in this\nrepository.\n\n## Project Overview\n\nsant"
},
{
"path": "docs/CLAUDE.html",
"chars": 12772,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\"><head><meta http-equiv=\"Content-Type\" "
},
{
"path": "docs/CLAUDE.md",
"chars": 3270,
"preview": "# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working\nwith code in this repository.\n\n## "
},
{
"path": "docs/LICENSE-text.html",
"chars": 4806,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\"><head><meta http-equiv=\"Content-Type\" "
},
{
"path": "docs/LICENSE-text.md",
"chars": 65,
"preview": "# License\n\n YEAR: 2020\n COPYRIGHT HOLDER: David Hugh-Jones\n"
},
{
"path": "docs/LICENSE.html",
"chars": 6065,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\"><head><meta http-equiv=\"Content-Type\" "
},
{
"path": "docs/LICENSE.md",
"chars": 1075,
"preview": "# MIT License\n\nCopyright (c) 2019 David Hugh-Jones\n\nPermission is hereby granted, free of charge, to any person obtainin"
},
{
"path": "docs/TODO.html",
"chars": 13442,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\"><head><meta http-equiv=\"Content-Type\" "
},
{
"path": "docs/TODO.md",
"chars": 6392,
"preview": "# TODO\n\n- Work on tests\n - tests for `left` and `close_end` arguments\n - tests for `brk_default`\n - [`brk_width()`](h"
},
{
"path": "docs/articles/index.html",
"chars": 5237,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\"><head><meta http-equiv=\"Content-Type\" "
},
{
"path": "docs/articles/index.md",
"chars": 320,
"preview": "# Articles\n\n### All vignettes\n\n- [Performance](https://hughjonesd.github.io/santoku/articles/website-articles/performanc"
},
{
"path": "docs/articles/santoku.html",
"chars": 67733,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\">\n<head>\n<meta http-equiv=\"Content-Type"
},
{
"path": "docs/articles/santoku.md",
"chars": 20182,
"preview": "# Introduction to santoku\n\n### Introduction\n\nSantoku is a package for cutting data into intervals. It provides\n[`chop()`"
},
{
"path": "docs/articles/santoku_files/accessible-code-block-0.0.1/empty-anchor.js",
"chars": 653,
"preview": "// Hide empty <a> tag within highlighted CodeBlock for screen reader accessibility (see https://github.com/jgm/pandoc/is"
},
{
"path": "docs/articles/santoku_files/header-attrs-2.11/header-attrs.js",
"chars": 507,
"preview": "// Pandoc 2.9 adds attributes on both header and div. We remove the former (to\n// be compatible with the behavior of Pan"
},
{
"path": "docs/articles/santoku_files/header-attrs-2.8/header-attrs.js",
"chars": 507,
"preview": "// Pandoc 2.9 adds attributes on both header and div. We remove the former (to\n// be compatible with the behavior of Pan"
},
{
"path": "docs/articles/website-articles/performance.html",
"chars": 22330,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\">\n<head>\n<meta http-equiv=\"Content-Type"
},
{
"path": "docs/articles/website-articles/performance.md",
"chars": 3689,
"preview": "# Performance\n\n## Speed\n\nThe core of santoku is written in C++. It is reasonably fast:\n\n``` r\n\n\npackageVersion(\"santoku\""
},
{
"path": "docs/articles/website-articles/performance_files/accessible-code-block-0.0.1/empty-anchor.js",
"chars": 653,
"preview": "// Hide empty <a> tag within highlighted CodeBlock for screen reader accessibility (see https://github.com/jgm/pandoc/is"
},
{
"path": "docs/articles/website-articles/performance_files/header-attrs-2.11/header-attrs.js",
"chars": 507,
"preview": "// Pandoc 2.9 adds attributes on both header and div. We remove the former (to\n// be compatible with the behavior of Pan"
},
{
"path": "docs/articles/website-articles/performance_files/header-attrs-2.8/header-attrs.js",
"chars": 507,
"preview": "// Pandoc 2.9 adds attributes on both header and div. We remove the former (to\n// be compatible with the behavior of Pan"
},
{
"path": "docs/articles/whats-new-in-0-9-0.html",
"chars": 17659,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\">\n<head>\n<meta http-equiv=\"Content-Type"
},
{
"path": "docs/articles/whats-new-in-0-9-0.md",
"chars": 4355,
"preview": "# What's new in santoku 0.9.0\n\nSantoku 0.9.0 has a few changes.\n\n## You can use break names for labels\n\nOn the command l"
},
{
"path": "docs/authors.html",
"chars": 5901,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\"><head><meta http-equiv=\"Content-Type\" "
},
{
"path": "docs/authors.md",
"chars": 565,
"preview": "# Authors and Citation\n\n## Authors\n\n- **David Hugh-Jones**. Author, maintainer.\n\n- **Daniel Possenriede**. Contributor.\n"
},
{
"path": "docs/bootstrap-toc.css",
"chars": 1843,
"preview": "/*!\n * Bootstrap Table of Contents v0.4.1 (http://afeld.github.io/bootstrap-toc/)\n * Copyright 2015 Aidan Feldman\n * Lic"
},
{
"path": "docs/bootstrap-toc.js",
"chars": 4764,
"preview": "/*!\n * Bootstrap Table of Contents v0.4.1 (http://afeld.github.io/bootstrap-toc/)\n * Copyright 2015 Aidan Feldman\n * Lic"
},
{
"path": "docs/deps/_Courier Prime-0.4.0/font.css",
"chars": 172,
"preview": "@font-face {\n font-family: 'Courier Prime';\n font-style: normal;\n font-weight: 400;\n font-display: swap;\n src: url("
},
{
"path": "docs/deps/bootstrap-5.1.0/font.css",
"chars": 478,
"preview": "@font-face {\n font-family: 'Lato';\n font-style: italic;\n font-weight: 400;\n font-display: swap;\n src: url(fonts/S6u"
},
{
"path": "docs/deps/bootstrap-5.1.3/font.css",
"chars": 478,
"preview": "@font-face {\n font-family: 'Lato';\n font-style: italic;\n font-weight: 400;\n font-display: swap;\n src: url(fonts/S6u"
},
{
"path": "docs/deps/bootstrap-5.2.2/font.css",
"chars": 478,
"preview": "@font-face {\n font-family: 'Lato';\n font-style: italic;\n font-weight: 400;\n font-display: swap;\n src: url(fonts/S6u"
},
{
"path": "docs/deps/bootstrap-5.3.1/font.css",
"chars": 2281,
"preview": "/* latin-ext */\n@font-face {\n font-family: 'Lato';\n font-style: italic;\n font-weight: 400;\n font-display: swap;\n sr"
},
{
"path": "docs/deps/data-deps.txt",
"chars": 885,
"preview": "<script src=\"deps/jquery-3.6.0/jquery-3.6.0.min.js\"></script>\n<meta name=\"viewport\" content=\"width=device-width, initial"
},
{
"path": "docs/deps/font-awesome-6.5.2/css/all.css",
"chars": 140418,
"preview": "/*!\n * Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com\n * License - https://fontawesome.com/license/fr"
},
{
"path": "docs/deps/font-awesome-6.5.2/css/v4-shims.css",
"chars": 41574,
"preview": "/*!\n * Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com\n * License - https://fontawesome.com/license/fr"
},
{
"path": "docs/deps/jquery-3.6.0/jquery-3.6.0.js",
"chars": 288580,
"preview": "/*!\n * jQuery JavaScript Library v3.6.0\n * https://jquery.com/\n *\n * Includes Sizzle.js\n * https://sizzlejs.com/\n *\n * C"
},
{
"path": "docs/docsearch.css",
"chars": 11758,
"preview": "/* Docsearch -------------------------------------------------------------- */\n/*\n Source: https://github.com/algolia/d"
},
{
"path": "docs/docsearch.js",
"chars": 2018,
"preview": "$(function() {\n\n // register a handler to move the focus to the search bar\n // upon pressing shift + \"/\" (i.e. \"?\")\n "
},
{
"path": "docs/extra.css",
"chars": 212,
"preview": "pre code span.fu {color: #F9EA6B;} /* function */\npre code span.kw {color: #F9EA6B;} /* keyword */\npre code "
},
{
"path": "docs/index.html",
"chars": 18641,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\">\n<head>\n<meta http-equiv=\"Content-Type"
},
{
"path": "docs/index.md",
"chars": 3552,
"preview": "# santoku\n\nsantoku is a versatile cutting tool for R. It provides\n[`chop()`](https://hughjonesd.github.io/santoku/refere"
},
{
"path": "docs/katex-auto.js",
"chars": 621,
"preview": "// https://github.com/jgm/pandoc/blob/29fa97ab96b8e2d62d48326e1b949a71dc41f47a/src/Text/Pandoc/Writers/HTML.hs#L332-L345"
},
{
"path": "docs/lightswitch.js",
"chars": 2498,
"preview": "\n/*!\n * Color mode toggler for Bootstrap's docs (https://getbootstrap.com/)\n * Copyright 2011-2023 The Bootstrap Authors"
},
{
"path": "docs/llms.txt",
"chars": 10453,
"preview": "# santoku\n\nsantoku is a versatile cutting tool for R. It provides\n[`chop()`](https://hughjonesd.github.io/santoku/refere"
},
{
"path": "docs/news/index.html",
"chars": 30720,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\"><head><meta http-equiv=\"Content-Type\" "
},
{
"path": "docs/news/index.md",
"chars": 17241,
"preview": "# Changelog\n\n## santoku 1.2.0\n\n- New experimental\n [`lbl_date()`](https://hughjonesd.github.io/santoku/reference/lbl_da"
},
{
"path": "docs/pkgdown.css",
"chars": 6932,
"preview": "/* Sticky footer */\n\n/**\n * Basic idea: https://philipwalton.github.io/solved-by-flexbox/demos/sticky-footer/\n * Details"
},
{
"path": "docs/pkgdown.js",
"chars": 4961,
"preview": "/* http://gregfranko.com/blog/jquery-best-practices/ */\n(function ($) {\n $(function () {\n\n $('nav.navbar').headroom("
},
{
"path": "docs/pkgdown.yml",
"chars": 343,
"preview": "pandoc: 3.8.3\npkgdown: 2.2.0\npkgdown_sha: ~\narticles:\n website-articles/performance: website-articles/performance.html\n"
},
{
"path": "docs/reference/breaks-class.html",
"chars": 6905,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\"><head><meta http-equiv=\"Content-Type\" "
},
{
"path": "docs/reference/breaks-class.md",
"chars": 268,
"preview": "# Class representing a set of intervals\n\nClass representing a set of intervals\n\n## Usage\n\n``` r\n# S3 method for class 'b"
},
{
"path": "docs/reference/brk-left-right.html",
"chars": 11982,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\"><head><meta http-equiv=\"Content-Type\" "
},
{
"path": "docs/reference/brk-left-right.md",
"chars": 1333,
"preview": "# Left- or right-closed breaks\n\n**\\[questioning\\]**\n\n## Usage\n\n``` r\nbrk_left(breaks)\n\nbrk_right(breaks)\n```\n\n## Argumen"
},
{
"path": "docs/reference/brk_default.html",
"chars": 8260,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\"><head><meta http-equiv=\"Content-Type\" "
},
{
"path": "docs/reference/brk_default.md",
"chars": 575,
"preview": "# Create a standard set of breaks\n\nCreate a standard set of breaks\n\n## Usage\n\n``` r\nbrk_default(breaks)\n```\n\n## Argument"
},
{
"path": "docs/reference/brk_equally.html",
"chars": 292,
"preview": "<html>\n <head>\n <meta http-equiv=\"refresh\" content=\"0;URL=https://hughjonesd.github.io/santoku/reference/chop_equall"
},
{
"path": "docs/reference/brk_evenly.html",
"chars": 290,
"preview": "<html>\n <head>\n <meta http-equiv=\"refresh\" content=\"0;URL=https://hughjonesd.github.io/santoku/reference/chop_evenly"
},
{
"path": "docs/reference/brk_fn.html",
"chars": 282,
"preview": "<html>\n <head>\n <meta http-equiv=\"refresh\" content=\"0;URL=https://hughjonesd.github.io/santoku/reference/chop_fn.htm"
},
{
"path": "docs/reference/brk_manual.html",
"chars": 11369,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\"><head><meta http-equiv=\"Content-Type\" "
},
{
"path": "docs/reference/brk_manual.md",
"chars": 1573,
"preview": "# Create a `breaks` object manually\n\nCreate a `breaks` object manually\n\n## Usage\n\n``` r\nbrk_manual(breaks, left_vec)\n```"
},
{
"path": "docs/reference/brk_mean_sd.html",
"chars": 292,
"preview": "<html>\n <head>\n <meta http-equiv=\"refresh\" content=\"0;URL=https://hughjonesd.github.io/santoku/reference/chop_mean_s"
},
{
"path": "docs/reference/brk_n.html",
"chars": 280,
"preview": "<html>\n <head>\n <meta http-equiv=\"refresh\" content=\"0;URL=https://hughjonesd.github.io/santoku/reference/chop_n.html"
},
{
"path": "docs/reference/brk_pretty.html",
"chars": 290,
"preview": "<html>\n <head>\n <meta http-equiv=\"refresh\" content=\"0;URL=https://hughjonesd.github.io/santoku/reference/chop_pretty"
},
{
"path": "docs/reference/brk_proportions.html",
"chars": 300,
"preview": "<html>\n <head>\n <meta http-equiv=\"refresh\" content=\"0;URL=https://hughjonesd.github.io/santoku/reference/chop_propor"
},
{
"path": "docs/reference/brk_quantiles.html",
"chars": 296,
"preview": "<html>\n <head>\n <meta http-equiv=\"refresh\" content=\"0;URL=https://hughjonesd.github.io/santoku/reference/chop_quanti"
},
{
"path": "docs/reference/brk_spikes.html",
"chars": 290,
"preview": "<html>\n <head>\n <meta http-equiv=\"refresh\" content=\"0;URL=https://hughjonesd.github.io/santoku/reference/chop_spikes"
},
{
"path": "docs/reference/brk_width-for-datetime.html",
"chars": 10430,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\"><head><meta http-equiv=\"Content-Type\" "
},
{
"path": "docs/reference/brk_width-for-datetime.md",
"chars": 1670,
"preview": "# Equal-width intervals for dates or datetimes\n\n[`brk_width()`](https://hughjonesd.github.io/santoku/reference/chop_widt"
},
{
"path": "docs/reference/brk_width.Duration.html",
"chars": 312,
"preview": "<html>\n <head>\n <meta http-equiv=\"refresh\" content=\"0;URL=https://hughjonesd.github.io/santoku/reference/brk_width-f"
},
{
"path": "docs/reference/brk_width.default.html",
"chars": 288,
"preview": "<html>\n <head>\n <meta http-equiv=\"refresh\" content=\"0;URL=https://hughjonesd.github.io/santoku/reference/chop_width."
},
{
"path": "docs/reference/brk_width.html",
"chars": 288,
"preview": "<html>\n <head>\n <meta http-equiv=\"refresh\" content=\"0;URL=https://hughjonesd.github.io/santoku/reference/chop_width."
},
{
"path": "docs/reference/chop.html",
"chars": 31533,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\"><head><meta http-equiv=\"Content-Type\" "
},
{
"path": "docs/reference/chop.md",
"chars": 9596,
"preview": "# Cut data into intervals\n\n`chop()` cuts `x` into intervals. It returns a\n[`factor`](https://rdrr.io/r/base/factor.html)"
},
{
"path": "docs/reference/chop_deciles.html",
"chars": 296,
"preview": "<html>\n <head>\n <meta http-equiv=\"refresh\" content=\"0;URL=https://hughjonesd.github.io/santoku/reference/chop_quanti"
},
{
"path": "docs/reference/chop_equally.html",
"chars": 13638,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\"><head><meta http-equiv=\"Content-Type\" "
},
{
"path": "docs/reference/chop_equally.md",
"chars": 2829,
"preview": "# Chop equal-sized groups\n\n`chop_equally()` chops `x` into groups with an equal number of elements.\n\n## Usage\n\n``` r\ncho"
},
{
"path": "docs/reference/chop_evenly.html",
"chars": 9470,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\"><head><meta http-equiv=\"Content-Type\" "
},
{
"path": "docs/reference/chop_evenly.md",
"chars": 1720,
"preview": "# Chop into equal-width intervals\n\n`chop_evenly()` chops `x` into `intervals` intervals of equal width.\n\n## Usage\n\n``` r"
},
{
"path": "docs/reference/chop_fn.html",
"chars": 12574,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\"><head><meta http-equiv=\"Content-Type\" "
},
{
"path": "docs/reference/chop_fn.md",
"chars": 2509,
"preview": "# Chop using an existing function\n\n`chop_fn()` is a convenience wrapper: `chop_fn(x, foo, ...)` is the same\nas `chop(x, "
},
{
"path": "docs/reference/chop_mean_sd.html",
"chars": 12074,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\"><head><meta http-equiv=\"Content-Type\" "
},
{
"path": "docs/reference/chop_mean_sd.md",
"chars": 2565,
"preview": "# Chop by standard deviations\n\nIntervals are measured in standard deviations on either side of the\nmean.\n\n## Usage\n\n``` "
},
{
"path": "docs/reference/chop_n.html",
"chars": 13555,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\"><head><meta http-equiv=\"Content-Type\" "
},
{
"path": "docs/reference/chop_n.md",
"chars": 2949,
"preview": "# Chop into fixed-sized groups\n\n`chop_n()` creates intervals containing a fixed number of elements.\n\n## Usage\n\n``` r\ncho"
},
{
"path": "docs/reference/chop_pretty.html",
"chars": 11132,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\"><head><meta http-equiv=\"Content-Type\" "
},
{
"path": "docs/reference/chop_pretty.md",
"chars": 1829,
"preview": "# Chop using pretty breakpoints\n\n`chop_pretty()` uses\n[`base::pretty()`](https://rdrr.io/r/base/pretty.html) to calculat"
},
{
"path": "docs/reference/chop_proportions.html",
"chars": 11095,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\"><head><meta http-equiv=\"Content-Type\" "
},
{
"path": "docs/reference/chop_proportions.md",
"chars": 2180,
"preview": "# Chop into proportions of the range of x\n\n`chop_proportions()` chops `x` into `proportions` of its range,\nexcluding inf"
},
{
"path": "docs/reference/chop_quantiles.html",
"chars": 20682,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\"><head><meta http-equiv=\"Content-Type\" "
},
{
"path": "docs/reference/chop_quantiles.md",
"chars": 5295,
"preview": "# Chop by quantiles\n\n`chop_quantiles()` chops data by quantiles. `chop_deciles()` is a\nconvenience function which chops "
},
{
"path": "docs/reference/chop_spikes.html",
"chars": 14820,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\"><head><meta http-equiv=\"Content-Type\" "
},
{
"path": "docs/reference/chop_spikes.md",
"chars": 3122,
"preview": "# Chop common values into singleton intervals\n\n`chop_spikes()` lets you chop common values of `x` into their own\nsinglet"
},
{
"path": "docs/reference/chop_width.html",
"chars": 13386,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\"><head><meta http-equiv=\"Content-Type\" "
},
{
"path": "docs/reference/chop_width.md",
"chars": 2762,
"preview": "# Chop into fixed-width intervals\n\n`chop_width()` chops `x` into intervals of fixed `width`.\n\n## Usage\n\n``` r\nchop_width"
},
{
"path": "docs/reference/dissect.html",
"chars": 17700,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\"><head><meta http-equiv=\"Content-Type\" "
},
{
"path": "docs/reference/dissect.md",
"chars": 3415,
"preview": "# Cut data into intervals, separating out common values\n\nSometimes it's useful to separate out common elements of `x`.\n`"
},
{
"path": "docs/reference/exactly.html",
"chars": 8802,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\"><head><meta http-equiv=\"Content-Type\" "
},
{
"path": "docs/reference/exactly.md",
"chars": 727,
"preview": "# Define singleton intervals explicitly\n\n`exactly()` duplicates its input. It lets you define singleton intervals\nlike t"
},
{
"path": "docs/reference/fillet.html",
"chars": 10139,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\"><head><meta http-equiv=\"Content-Type\" "
},
{
"path": "docs/reference/fillet.md",
"chars": 2051,
"preview": "# Chop data precisely (for programmers)\n\n`fillet()` calls\n[`chop()`](https://hughjonesd.github.io/santoku/reference/chop"
},
{
"path": "docs/reference/format.breaks.html",
"chars": 292,
"preview": "<html>\n <head>\n <meta http-equiv=\"refresh\" content=\"0;URL=https://hughjonesd.github.io/santoku/reference/breaks-clas"
},
{
"path": "docs/reference/index.html",
"chars": 13632,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\"><head><meta http-equiv=\"Content-Type\" "
},
{
"path": "docs/reference/index.md",
"chars": 6579,
"preview": "# Package index\n\n## Package overview\n\n- [`santoku`](https://hughjonesd.github.io/santoku/reference/santoku-package.md)\n "
},
{
"path": "docs/reference/is.breaks.html",
"chars": 292,
"preview": "<html>\n <head>\n <meta http-equiv=\"refresh\" content=\"0;URL=https://hughjonesd.github.io/santoku/reference/breaks-clas"
},
{
"path": "docs/reference/kiru.html",
"chars": 276,
"preview": "<html>\n <head>\n <meta http-equiv=\"refresh\" content=\"0;URL=https://hughjonesd.github.io/santoku/reference/chop.html\" "
},
{
"path": "docs/reference/knife.html",
"chars": 8406,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\"><head><meta http-equiv=\"Content-Type\" "
},
{
"path": "docs/reference/knife.md",
"chars": 314,
"preview": "# Deprecated\n\n**\\[soft-deprecated\\]** `knife()` is deprecated in favour of\n[`purrr::partial()`](https://purrr.tidyverse."
},
{
"path": "docs/reference/lbl_dash.html",
"chars": 15380,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\"><head><meta http-equiv=\"Content-Type\" "
},
{
"path": "docs/reference/lbl_dash.md",
"chars": 3745,
"preview": "# Label chopped intervals like 1-4, 4-5, ...\n\nThis label style is user-friendly, but doesn't distinguish between left-\na"
},
{
"path": "docs/reference/lbl_datetime.html",
"chars": 16322,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\"><head><meta http-equiv=\"Content-Type\" "
},
{
"path": "docs/reference/lbl_datetime.md",
"chars": 4141,
"preview": "# Label dates and datetimes\n\n**\\[experimental\\]**\n\n`lbl_date()` and `lbl_datetime()` produce nice labels for dates and\nd"
},
{
"path": "docs/reference/lbl_discrete.html",
"chars": 14116,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\"><head><meta http-equiv=\"Content-Type\" "
},
{
"path": "docs/reference/lbl_discrete.md",
"chars": 3350,
"preview": "# Label discrete data\n\n`lbl_discrete()` creates labels for discrete data, such as integers. For\nexample, breaks `c(1, 3,"
},
{
"path": "docs/reference/lbl_endpoint.html",
"chars": 294,
"preview": "<html>\n <head>\n <meta http-equiv=\"refresh\" content=\"0;URL=https://hughjonesd.github.io/santoku/reference/lbl_endpoin"
},
{
"path": "docs/reference/lbl_endpoints.html",
"chars": 15537,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\"><head><meta http-equiv=\"Content-Type\" "
},
{
"path": "docs/reference/lbl_endpoints.md",
"chars": 3557,
"preview": "# Label chopped intervals by their left or right endpoints\n\nThis is useful when the left endpoint unambiguously indicate"
},
{
"path": "docs/reference/lbl_format.html",
"chars": 13926,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\"><head><meta http-equiv=\"Content-Type\" "
},
{
"path": "docs/reference/lbl_format.md",
"chars": 2349,
"preview": "# Label chopped intervals with arbitrary formatting\n\n**\\[questioning\\]**\n\n## Usage\n\n``` r\nlbl_format(fmt, fmt1 = \"%.3g\","
},
{
"path": "docs/reference/lbl_glue.html",
"chars": 15732,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\"><head><meta http-equiv=\"Content-Type\" "
},
{
"path": "docs/reference/lbl_glue.md",
"chars": 3887,
"preview": "# Label chopped intervals using the `glue` package\n\nUse `\"{l}\"` and `\"{r}\"` to show the left and right endpoints of the\n"
},
{
"path": "docs/reference/lbl_intervals.html",
"chars": 14615,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\"><head><meta http-equiv=\"Content-Type\" "
},
{
"path": "docs/reference/lbl_intervals.md",
"chars": 3781,
"preview": "# Label chopped intervals using set notation\n\nThese labels are the most exact, since they show you whether intervals\nare"
},
{
"path": "docs/reference/lbl_manual.html",
"chars": 11006,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\"><head><meta http-equiv=\"Content-Type\" "
},
{
"path": "docs/reference/lbl_manual.md",
"chars": 2130,
"preview": "# Defunct: label chopped intervals in a user-defined sequence\n\n**\\[defunct\\]**\n\n## Usage\n\n``` r\nlbl_manual(sequence, fmt"
},
{
"path": "docs/reference/lbl_midpoints.html",
"chars": 10930,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\"><head><meta http-equiv=\"Content-Type\" "
},
{
"path": "docs/reference/lbl_midpoints.md",
"chars": 2715,
"preview": "# Label chopped intervals by their midpoints\n\nThis uses the midpoint of each interval for its label.\n\n## Usage\n\n``` r\nlb"
},
{
"path": "docs/reference/lbl_seq.html",
"chars": 10428,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\"><head><meta http-equiv=\"Content-Type\" "
},
{
"path": "docs/reference/lbl_seq.md",
"chars": 1824,
"preview": "# Label chopped intervals in sequence\n\n`lbl_seq()` labels intervals sequentially, using numbers or letters.\n\n## Usage\n\n`"
},
{
"path": "docs/reference/non-standard-types.html",
"chars": 7086,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\"><head><meta http-equiv=\"Content-Type\" "
},
{
"path": "docs/reference/non-standard-types.md",
"chars": 1006,
"preview": "# Tips for chopping non-standard types\n\nSantoku can handle many non-standard types.\n\n## Details\n\n- If objects can be com"
},
{
"path": "docs/reference/percent.html",
"chars": 7049,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\"><head><meta http-equiv=\"Content-Type\" "
},
{
"path": "docs/reference/percent.md",
"chars": 341,
"preview": "# Simple percentage formatter\n\n`percent()` formats `x` as a percentage. For a wider range of\nformatters, consider the [`"
},
{
"path": "docs/reference/print.breaks.html",
"chars": 292,
"preview": "<html>\n <head>\n <meta http-equiv=\"refresh\" content=\"0;URL=https://hughjonesd.github.io/santoku/reference/breaks-clas"
},
{
"path": "docs/reference/santoku-cast.html",
"chars": 8245,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\"><head><meta http-equiv=\"Content-Type\" "
},
{
"path": "docs/reference/santoku-cast.md",
"chars": 631,
"preview": "# Internal functions\n\nInternal functions\n\n## Usage\n\n``` r\n# Default S3 method\nsantoku_cast_common(x, y)\n\n# S3 method for"
},
{
"path": "docs/reference/santoku-package.html",
"chars": 9738,
"preview": "<!DOCTYPE html>\n<!-- Generated by pkgdown: do not edit by hand --><html lang=\"en\"><head><meta http-equiv=\"Content-Type\" "
},
{
"path": "docs/reference/santoku-package.md",
"chars": 2267,
"preview": "# A versatile cutting tool for R: package overview and options\n\nsantoku is a tool for cutting data into intervals. It pr"
},
{
"path": "docs/reference/santoku.html",
"chars": 298,
"preview": "<html>\n <head>\n <meta http-equiv=\"refresh\" content=\"0;URL=https://hughjonesd.github.io/santoku/reference/santoku-pac"
},
{
"path": "docs/reference/santoku_cast_common.Date.html",
"chars": 292,
"preview": "<html>\n <head>\n <meta http-equiv=\"refresh\" content=\"0;URL=https://hughjonesd.github.io/santoku/reference/santoku-cas"
},
{
"path": "docs/reference/santoku_cast_common.POSIXct.html",
"chars": 292,
"preview": "<html>\n <head>\n <meta http-equiv=\"refresh\" content=\"0;URL=https://hughjonesd.github.io/santoku/reference/santoku-cas"
},
{
"path": "docs/reference/santoku_cast_common.default.html",
"chars": 292,
"preview": "<html>\n <head>\n <meta http-equiv=\"refresh\" content=\"0;URL=https://hughjonesd.github.io/santoku/reference/santoku-cas"
},
{
"path": "docs/reference/santoku_cast_common.double.html",
"chars": 292,
"preview": "<html>\n <head>\n <meta http-equiv=\"refresh\" content=\"0;URL=https://hughjonesd.github.io/santoku/reference/santoku-cas"
},
{
"path": "docs/reference/santoku_cast_common.hexmode.html",
"chars": 292,
"preview": "<html>\n <head>\n <meta http-equiv=\"refresh\" content=\"0;URL=https://hughjonesd.github.io/santoku/reference/santoku-cas"
},
{
"path": "docs/reference/santoku_cast_common.integer64.html",
"chars": 292,
"preview": "<html>\n <head>\n <meta http-equiv=\"refresh\" content=\"0;URL=https://hughjonesd.github.io/santoku/reference/santoku-cas"
},
{
"path": "docs/reference/santoku_cast_common.octmode.html",
"chars": 292,
"preview": "<html>\n <head>\n <meta http-equiv=\"refresh\" content=\"0;URL=https://hughjonesd.github.io/santoku/reference/santoku-cas"
},
{
"path": "docs/reference/santoku_cast_common.ts.html",
"chars": 292,
"preview": "<html>\n <head>\n <meta http-equiv=\"refresh\" content=\"0;URL=https://hughjonesd.github.io/santoku/reference/santoku-cas"
},
{
"path": "docs/reference/santoku_cast_common.zoo.html",
"chars": 292,
"preview": "<html>\n <head>\n <meta http-equiv=\"refresh\" content=\"0;URL=https://hughjonesd.github.io/santoku/reference/santoku-cas"
},
{
"path": "docs/reference/sequence-labels.html",
"chars": 9055,
"preview": "<!-- Generated by pkgdown: do not edit by hand -->\n<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\">\n<m"
},
{
"path": "docs/reference/tab.html",
"chars": 276,
"preview": "<html>\n <head>\n <meta http-equiv=\"refresh\" content=\"0;URL=https://hughjonesd.github.io/santoku/reference/chop.html\" "
},
{
"path": "docs/reference/tab_deciles.html",
"chars": 296,
"preview": "<html>\n <head>\n <meta http-equiv=\"refresh\" content=\"0;URL=https://hughjonesd.github.io/santoku/reference/chop_quanti"
},
{
"path": "docs/reference/tab_dissect.html",
"chars": 282,
"preview": "<html>\n <head>\n <meta http-equiv=\"refresh\" content=\"0;URL=https://hughjonesd.github.io/santoku/reference/dissect.htm"
},
{
"path": "docs/reference/tab_equally.html",
"chars": 292,
"preview": "<html>\n <head>\n <meta http-equiv=\"refresh\" content=\"0;URL=https://hughjonesd.github.io/santoku/reference/chop_equall"
},
{
"path": "docs/reference/tab_evenly.html",
"chars": 290,
"preview": "<html>\n <head>\n <meta http-equiv=\"refresh\" content=\"0;URL=https://hughjonesd.github.io/santoku/reference/chop_evenly"
},
{
"path": "docs/reference/tab_fn.html",
"chars": 282,
"preview": "<html>\n <head>\n <meta http-equiv=\"refresh\" content=\"0;URL=https://hughjonesd.github.io/santoku/reference/chop_fn.htm"
},
{
"path": "docs/reference/tab_mean_sd.html",
"chars": 292,
"preview": "<html>\n <head>\n <meta http-equiv=\"refresh\" content=\"0;URL=https://hughjonesd.github.io/santoku/reference/chop_mean_s"
},
{
"path": "docs/reference/tab_n.html",
"chars": 280,
"preview": "<html>\n <head>\n <meta http-equiv=\"refresh\" content=\"0;URL=https://hughjonesd.github.io/santoku/reference/chop_n.html"
},
{
"path": "docs/reference/tab_pretty.html",
"chars": 290,
"preview": "<html>\n <head>\n <meta http-equiv=\"refresh\" content=\"0;URL=https://hughjonesd.github.io/santoku/reference/chop_pretty"
}
]
// ... and 87 more files (download for full content)
About this extraction
This page contains the full source code of the hughjonesd/santoku GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 287 files (2.2 MB), approximately 592.8k tokens, and a symbol index with 361 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.