├── .Rbuildignore ├── .gitattributes ├── .github ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ └── issue_template.md ├── SUPPORT.md └── workflows │ ├── R-CMD-check.yaml │ └── test-coverage.yaml ├── .gitignore ├── DESCRIPTION ├── LICENSE ├── LICENSE.md ├── NAMESPACE ├── NEWS.md ├── R ├── add-date-columns.R ├── add-headline-column.R ├── compare-conditions.R ├── compare-values.R ├── data.R ├── demo-data.R ├── headline.R ├── plural-phrasing.R ├── string-manipulation.R ├── trend-terms.R ├── utils-data-manipulation.R ├── utils-dates.R └── view-list.R ├── README.Rmd ├── README.md ├── _pkgdown.yml ├── codecov.yml ├── cran-comments.md ├── data-raw └── pixar.R ├── data └── pixar_films.rda ├── docs ├── 404.html ├── CODE_OF_CONDUCT.html ├── CONTRIBUTING.html ├── LICENSE-text.html ├── LICENSE.html ├── SUPPORT.html ├── apple-touch-icon-120x120.png ├── apple-touch-icon-152x152.png ├── apple-touch-icon-180x180.png ├── apple-touch-icon-60x60.png ├── apple-touch-icon-76x76.png ├── apple-touch-icon.png ├── articles │ ├── index.html │ └── intro.html ├── authors.html ├── bootstrap-toc.css ├── bootstrap-toc.js ├── deps │ ├── bootstrap-5.1.0 │ │ ├── bootstrap.bundle.min.js │ │ ├── bootstrap.bundle.min.js.map │ │ └── bootstrap.min.css │ ├── data-deps.txt │ └── jquery-3.6.0 │ │ ├── jquery-3.6.0.js │ │ ├── jquery-3.6.0.min.js │ │ └── jquery-3.6.0.min.map ├── docsearch.css ├── docsearch.js ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── index.html ├── link.svg ├── logo.svg ├── news │ └── index.html ├── pkgdown.css ├── pkgdown.js ├── pkgdown.yml ├── reference │ ├── Rplot001.png │ ├── Rplot002.png │ ├── Rplot003.png │ ├── add_article.html │ ├── add_date_columns.html │ ├── add_headline_column.html │ ├── animal_sleep.html │ ├── compare_conditions.html │ ├── compare_values.html │ ├── demo_data.html │ ├── figures │ │ ├── facts_vs_insights.png │ │ ├── logo.png │ │ ├── logo.svg │ │ └── value_box.png │ ├── flights_jfk.html │ ├── headline.html │ ├── index.html │ ├── pipe.html │ ├── pixar_films-1.png │ ├── pixar_films-2.png │ ├── pixar_films-3.png │ ├── pixar_films.html │ ├── plural_phrasing.html │ ├── trend_terms.html │ └── view_list.html ├── search.json └── sitemap.xml ├── headliner.Rproj ├── inst └── WORDLIST ├── man ├── add_article.Rd ├── add_date_columns.Rd ├── add_headline_column.Rd ├── compare_conditions.Rd ├── compare_values.Rd ├── demo_data.Rd ├── figures │ ├── facts_vs_insights.png │ ├── logo.png │ ├── logo.svg │ └── value_box.png ├── headline.Rd ├── pixar_films.Rd ├── plural_phrasing.Rd ├── trend_terms.Rd └── view_list.Rd ├── pkgdown └── favicon │ ├── apple-touch-icon-120x120.png │ ├── apple-touch-icon-152x152.png │ ├── apple-touch-icon-180x180.png │ ├── apple-touch-icon-60x60.png │ ├── apple-touch-icon-76x76.png │ ├── apple-touch-icon.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ └── favicon.ico ├── tests ├── testthat.R └── testthat │ ├── test-add-date-columns.R │ ├── test-add-headline-column.R │ ├── test-compare-conditions.R │ ├── test-compare-values.R │ ├── test-demo-data.R │ ├── test-headline.R │ ├── test-plural-phrasing.R │ ├── test-string-manipulation.R │ ├── test-utils-data-manipulation.R │ ├── test-utils-dates.R │ └── test-view-list.R └── vignettes ├── .gitignore └── intro.Rmd /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^headliner\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^LICENSE\.md$ 4 | ^README\.Rmd$ 5 | ^cran-comments\.md$ 6 | ^\.github$ 7 | ^data-raw$ 8 | ^docs$ 9 | ^pkgdown$ 10 | ^_pkgdown\.yml$ 11 | ^codecov\.yml$ 12 | man/figures/logo.svg 13 | ^CRAN-SUBMISSION$ 14 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the overall 26 | community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards 42 | of acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies 54 | when an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail 56 | address, posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at [INSERT CONTACT 63 | METHOD]. All complaints will be reviewed and investigated promptly and fairly. 64 | 65 | All community leaders are obligated to respect the privacy and security of the 66 | reporter of any incident. 67 | 68 | ## Enforcement Guidelines 69 | 70 | Community leaders will follow these Community Impact Guidelines in determining 71 | the consequences for any action they deem in violation of this Code of Conduct: 72 | 73 | ### 1. Correction 74 | 75 | **Community Impact**: Use of inappropriate language or other behavior deemed 76 | unprofessional or unwelcome in the community. 77 | 78 | **Consequence**: A private, written warning from community leaders, providing 79 | clarity around the nature of the violation and an explanation of why the 80 | behavior was inappropriate. A public apology may be requested. 81 | 82 | ### 2. Warning 83 | 84 | **Community Impact**: A violation through a single incident or series of 85 | actions. 86 | 87 | **Consequence**: A warning with consequences for continued behavior. No 88 | interaction with the people involved, including unsolicited interaction with 89 | those enforcing the Code of Conduct, for a specified period of time. This 90 | includes avoiding interactions in community spaces as well as external channels 91 | like social media. Violating these terms may lead to a temporary or permanent 92 | ban. 93 | 94 | ### 3. Temporary Ban 95 | 96 | **Community Impact**: A serious violation of community standards, including 97 | sustained inappropriate behavior. 98 | 99 | **Consequence**: A temporary ban from any sort of interaction or public 100 | communication with the community for a specified period of time. No public or 101 | private interaction with the people involved, including unsolicited interaction 102 | with those enforcing the Code of Conduct, is allowed during this period. 103 | Violating these terms may lead to a permanent ban. 104 | 105 | ### 4. Permanent Ban 106 | 107 | **Community Impact**: Demonstrating a pattern of violation of community 108 | standards, including sustained inappropriate behavior, harassment of an 109 | individual, or aggression toward or disparagement of classes of individuals. 110 | 111 | **Consequence**: A permanent ban from any sort of public interaction within the 112 | community. 113 | 114 | ## Attribution 115 | 116 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 117 | version 2.0, 118 | available at https://www.contributor-covenant.org/version/2/0/ 119 | code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at https:// 128 | www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to headliner 2 | 3 | This outlines how to propose a change to headliner. 4 | For more detailed info about contributing to this, and other tidyverse packages, please see the 5 | [**development contributing guide**](https://rstd.io/tidy-contrib). 6 | 7 | ## Fixing typos 8 | 9 | You can fix typos, spelling mistakes, or grammatical errors in the documentation directly using the GitHub web interface, as long as the changes are made in the _source_ file. 10 | This generally means you'll need to edit [roxygen2 comments](https://roxygen2.r-lib.org/articles/roxygen2.html) in an `.R`, not a `.Rd` file. 11 | You can find the `.R` file that generates the `.Rd` by reading the comment in the first line. 12 | 13 | ## Bigger changes 14 | 15 | If you want to make a bigger change, it's a good idea to first file an issue and make sure someone from the team agrees that it’s needed. 16 | If you’ve found a bug, please file an issue that illustrates the bug with a minimal 17 | [reprex](https://www.tidyverse.org/help/#reprex) (this will also help you write a unit test, if needed). 18 | 19 | ### Pull request process 20 | 21 | * Fork the package and clone onto your computer. If you haven't done this before, we recommend using `usethis::create_from_github("", fork = TRUE)`. 22 | 23 | * Install all development dependences with `devtools::install_dev_deps()`, and then make sure the package passes R CMD check by running `devtools::check()`. 24 | If R CMD check doesn't pass cleanly, it's a good idea to ask for help before continuing. 25 | * Create a Git branch for your pull request (PR). We recommend using `usethis::pr_init("brief-description-of-change")`. 26 | 27 | * Make your changes, commit to git, and then create a PR by running `usethis::pr_push()`, and following the prompts in your browser. 28 | The title of your PR should briefly describe the change. 29 | The body of your PR should contain `Fixes #issue-number`. 30 | 31 | * For user-facing changes, add a bullet to the top of `NEWS.md` (i.e. just below the first header). Follow the style described in . 32 | 33 | ### Code style 34 | 35 | * New code should follow the tidyverse [style guide](https://style.tidyverse.org). 36 | You can use the [styler](https://CRAN.R-project.org/package=styler) package to apply these styles, but please don't restyle code that has nothing to do with your PR. 37 | 38 | * We use [roxygen2](https://cran.r-project.org/package=roxygen2), with [Markdown syntax](https://cran.r-project.org/web/packages/roxygen2/vignettes/rd-formatting.html), for documentation. 39 | 40 | * We use [testthat](https://cran.r-project.org/package=testthat) for unit tests. 41 | Contributions with test cases included are easier to accept. 42 | 43 | ## Code of Conduct 44 | 45 | Please note that the headliner project is released with a 46 | [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By contributing to this 47 | project you agree to abide by its terms. 48 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/issue_template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report or feature request 3 | about: Describe a bug you've seen or make a case for a new feature 4 | --- 5 | 6 | Please briefly describe your problem and what output you expect. If you have a question, please don't use this form. Instead, ask on or . 7 | 8 | Please include a minimal reproducible example (AKA a reprex). If you've never heard of a [reprex](https://reprex.tidyverse.org/) before, start by reading . 9 | 10 | Brief description of the problem 11 | 12 | ```r 13 | # insert reprex here 14 | ``` 15 | -------------------------------------------------------------------------------- /.github/SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Getting help with headliner 2 | 3 | Thanks for using headliner! 4 | Before filing an issue, there are a few places to explore and pieces to put together to make the process as smooth as possible. 5 | 6 | ## Make a reprex 7 | 8 | Start by making a minimal **repr**oducible **ex**ample using the [reprex](https://reprex.tidyverse.org/) package. 9 | If you haven't heard of or used reprex before, you're in for a treat! 10 | Seriously, reprex will make all of your R-question-asking endeavors easier (which is a pretty insane ROI for the five to ten minutes it'll take you to learn what it's all about). 11 | For additional reprex pointers, check out the [Get help!](https://www.tidyverse.org/help/) section of the tidyverse site. 12 | 13 | ## Where to ask? 14 | 15 | Armed with your reprex, the next step is to figure out [where to ask](https://www.tidyverse.org/help/#where-to-ask). 16 | 17 | * If it's a question: start with [community.rstudio.com](https://community.rstudio.com/), and/or StackOverflow. There are more people there to answer questions. 18 | 19 | * If it's a bug: you're in the right place, [file an issue](https://github.com//issues/new). 20 | 21 | * If you're not sure: let the community help you figure it out! 22 | If your problem _is_ a bug or a feature request, you can easily return here and report it. 23 | 24 | Before opening a new issue, be sure to [search issues and pull requests](https://github.com//issues) to make sure the bug hasn't been reported and/or already fixed in the development version. 25 | By default, the search will be pre-populated with `is:issue is:open`. 26 | You can [edit the qualifiers](https://help.github.com/articles/searching-issues-and-pull-requests/) (e.g. `is:pr`, `is:closed`) as needed. 27 | For example, you'd simply remove `is:open` to search _all_ issues in the repo, open or closed. 28 | 29 | ## What happens next? 30 | 31 | To be as efficient as possible, development of tidyverse packages tends to be very bursty, so you shouldn't worry if you don't get an immediate response. 32 | Typically we don't look at a repo until a sufficient quantity of issues accumulates, then there’s a burst of intense activity as we focus our efforts. 33 | That makes development more efficient because it avoids expensive context switching between problems, at the cost of taking longer to get back to you. 34 | This process makes a good reprex particularly important because it might be multiple months between your initial report and when we start working on it. 35 | If we can’t reproduce the bug, we can’t fix it! 36 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | # added via usethis::use_github_action_check_standard() 2 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 3 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 4 | on: 5 | push: 6 | branches: [main, master] 7 | pull_request: 8 | branches: [main, master] 9 | 10 | name: R-CMD-check 11 | 12 | jobs: 13 | R-CMD-check: 14 | runs-on: ${{ matrix.config.os }} 15 | 16 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 17 | 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | config: 22 | - {os: macOS-latest, r: 'release'} 23 | - {os: windows-latest, r: 'release'} 24 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} 25 | - {os: ubuntu-latest, r: 'release'} 26 | - {os: ubuntu-latest, r: 'oldrel-1'} 27 | 28 | env: 29 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 30 | R_KEEP_PKG_SOURCE: yes 31 | 32 | steps: 33 | - uses: actions/checkout@v2 34 | 35 | - uses: r-lib/actions/setup-pandoc@v2 36 | 37 | - uses: r-lib/actions/setup-r@v2 38 | with: 39 | r-version: ${{ matrix.config.r }} 40 | http-user-agent: ${{ matrix.config.http-user-agent }} 41 | use-public-rspm: true 42 | 43 | - uses: r-lib/actions/setup-r-dependencies@v2 44 | with: 45 | extra-packages: any::rcmdcheck 46 | needs: check 47 | 48 | - uses: r-lib/actions/check-r-package@v2 49 | with: 50 | upload-snapshots: true 51 | -------------------------------------------------------------------------------- /.github/workflows/test-coverage.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | branches: [main, master] 8 | 9 | name: test-coverage 10 | 11 | jobs: 12 | test-coverage: 13 | runs-on: ubuntu-latest 14 | env: 15 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | 20 | - uses: r-lib/actions/setup-r@v2 21 | with: 22 | use-public-rspm: true 23 | 24 | - uses: r-lib/actions/setup-r-dependencies@v2 25 | with: 26 | extra-packages: any::covr 27 | needs: coverage 28 | 29 | - name: Test coverage 30 | run: covr::codecov(quiet = FALSE) 31 | shell: Rscript {0} 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # History files 2 | .Rhistory 3 | .Rapp.history 4 | 5 | # Session Data files 6 | .RData 7 | 8 | # User-specific files 9 | .Ruserdata 10 | 11 | # Example code in package build process 12 | *-Ex.R 13 | 14 | # Output files from R CMD build 15 | /*.tar.gz 16 | 17 | # Output files from R CMD check 18 | /*.Rcheck/ 19 | 20 | # RStudio files 21 | .Rproj.user/ 22 | 23 | # produced vignettes 24 | vignettes/*.html 25 | vignettes/*.pdf 26 | 27 | # OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3 28 | .httr-oauth 29 | 30 | # knitr and R markdown default cache directories 31 | *_cache/ 32 | /cache/ 33 | 34 | # Temporary files created by R markdown 35 | *.utf8.md 36 | *.knit.md 37 | .Rproj.user 38 | inst/doc 39 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: headliner 2 | Title: Compose Sentences to Describe Comparisons 3 | Version: 0.0.3 4 | Authors@R: 5 | person( 6 | given = "Jake", 7 | family = "Riley", 8 | role = c("aut", "cre"), 9 | email = "rjake@sas.upenn.edu" 10 | ) 11 | Description: Create dynamic, data-driven text. Given two values, a list of 12 | talking points is generated and can be combined using string 13 | interpolation. Based on the 'glue' package. 14 | License: MIT + file LICENSE 15 | URL: https://rjake.github.io/headliner/, https://github.com/rjake/headliner/ 16 | BugReports: https://github.com/rjake/headliner/issues/ 17 | Depends: 18 | R (>= 4.1) 19 | Imports: 20 | dplyr, 21 | glue, 22 | lubridate, 23 | purrr, 24 | rlang, 25 | tibble, 26 | tidyr 27 | Suggests: 28 | ggplot2, 29 | knitr, 30 | nycflights13, 31 | rmarkdown, 32 | testthat 33 | VignetteBuilder: knitr 34 | Encoding: UTF-8 35 | Language: en-US 36 | LazyData: true 37 | Roxygen: list(markdown = TRUE) 38 | RoxygenNote: 7.2.0 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2020 2 | COPYRIGHT HOLDER: Jake Riley 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2020 Jake Riley 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(add_article) 4 | export(add_date_columns) 5 | export(add_headline_column) 6 | export(compare_conditions) 7 | export(compare_values) 8 | export(demo_data) 9 | export(headline) 10 | export(headline_list) 11 | export(plural_phrasing) 12 | export(trend_terms) 13 | export(view_list) 14 | importFrom(dplyr,across) 15 | importFrom(dplyr,bind_cols) 16 | importFrom(dplyr,case_when) 17 | importFrom(dplyr,everything) 18 | importFrom(dplyr,filter) 19 | importFrom(dplyr,group_keys) 20 | importFrom(dplyr,group_vars) 21 | importFrom(dplyr,left_join) 22 | importFrom(dplyr,lst) 23 | importFrom(dplyr,mutate) 24 | importFrom(dplyr,one_of) 25 | importFrom(dplyr,pull) 26 | importFrom(dplyr,relocate) 27 | importFrom(dplyr,rowwise) 28 | importFrom(dplyr,select) 29 | importFrom(dplyr,summarise) 30 | importFrom(dplyr,transmute) 31 | importFrom(dplyr,ungroup) 32 | importFrom(glue,as_glue) 33 | importFrom(glue,glue) 34 | importFrom(glue,glue_collapse) 35 | importFrom(glue,glue_data) 36 | importFrom(lubridate,`%m+%`) 37 | importFrom(lubridate,floor_date) 38 | importFrom(lubridate,interval) 39 | importFrom(lubridate,period) 40 | importFrom(purrr,flatten) 41 | importFrom(purrr,map) 42 | importFrom(purrr,map2) 43 | importFrom(purrr,map2_chr) 44 | importFrom(purrr,map_dbl) 45 | importFrom(purrr,map_dfr) 46 | importFrom(purrr,map_if) 47 | importFrom(purrr,pluck) 48 | importFrom(purrr,pmap) 49 | importFrom(purrr,possibly) 50 | importFrom(rlang,":=") 51 | importFrom(rlang,.data) 52 | importFrom(rlang,abort) 53 | importFrom(rlang,enquo) 54 | importFrom(rlang,quo_is_missing) 55 | importFrom(rlang,warn) 56 | importFrom(tibble,tibble) 57 | importFrom(tidyr,unnest_wider) 58 | importFrom(utils,head) 59 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # headliner 0.0.3 2 | - Patch fix ahead of `dplyr` 1.1.0 (#121) 3 | - Reformat `NEWS.md` headers (#117) 4 | 5 | # headliner 0.0.2 6 | ### New Features 7 | - `add_headline_column()` now allows headlines to be passed in an existing column (#113) 8 | 9 | ### Bug fixes 10 | - `headline()` no longer throws an error if NA (#110) 11 | - `compare_values()` to give informative error if length of `x` or `y` > 1 (#110) 12 | 13 | ### Other 14 | - `"same"` argument removed from `trend_terms()` as it is not being utilized (#115) 15 | 16 | 17 | # headliner 0.0.1 18 | - Initial release 19 | -------------------------------------------------------------------------------- /R/add-date-columns.R: -------------------------------------------------------------------------------- 1 | #' Add columns with date calculations based on reference date 2 | #' 3 | #' @return Returns a data frame with columns appended to describe 4 | #' date distances from a reference date. 5 | #' 6 | #' @description Using a reference date (defaults to current date), columns are 7 | #' appended to the data set describing the number of days, weeks, months, 8 | #' quarters, calendar years and fiscal years since the reference date. If 9 | #' the new columns share names with an existing column, the function will show 10 | #' a warning. 11 | #' 12 | #' @param df data frame 13 | #' @param date_col column with class of 'date' 14 | #' @param ref_date reference date for calculations, defaults to current date 15 | #' @param fiscal_year_offset the number of months to offset date, if fiscal 16 | #' year ends in June, use 6 17 | #' @param week_start integer for start of week where Monday = 1 and Sunday = 7 18 | #' @param drop some of the generated fields may match the input data frame. When 19 | #' TRUE, the original columns will be removed and replaced with the new field 20 | #' of the same name. Otherwise, columns with the same name will be appended with 21 | #' a '1' 22 | #' @importFrom dplyr pull select bind_cols 23 | #' @importFrom tibble tibble 24 | #' @importFrom lubridate floor_date 25 | #' @export 26 | #' 27 | #' @examples 28 | #' demo_data() |> 29 | #' add_date_columns(date_col = date) 30 | #' 31 | #' # if columns overlap, you will see a warning 32 | #' demo_data() |> 33 | #' dplyr::mutate(week = 1) |> 34 | #' add_date_columns(date_col = date) 35 | #' 36 | #' # to drop the old column and keep the new column use `drop = TRUE` 37 | #' demo_data() |> 38 | #' dplyr::mutate(week = 1) |> 39 | #' add_date_columns(date_col = date, drop = TRUE) 40 | 41 | add_date_columns <- function(df, 42 | date_col, 43 | ref_date = Sys.Date(), 44 | fiscal_year_offset = 6, 45 | week_start = 1, 46 | drop = FALSE) { 47 | x <- pull(df, {{date_col}}) 48 | offset <- fiscal_year_offset 49 | 50 | new_fields <- 51 | tibble( 52 | day = calc_distance(x, "day", to = ref_date), 53 | week = calc_distance(x, "week", to = ref_date, week_start = week_start), 54 | month = calc_distance(x, "month", to = ref_date), 55 | quarter = 56 | calc_distance( 57 | from = floor_date(x, "quarter"), 58 | unit = "month", 59 | n = 3, 60 | to = floor_date(ref_date, "quarter") 61 | ), 62 | calendar_year = calc_distance(x, "year", to = ref_date), 63 | fiscal_year = 64 | calc_distance( 65 | from = fiscal_date(x, offset), 66 | unit = "year", 67 | to = fiscal_date(ref_date, offset) 68 | ) 69 | ) 70 | 71 | overlap <- check_overlapping_names(df, new_fields, drop = drop) 72 | 73 | if (drop) { 74 | df <- select(df, -overlap) 75 | } 76 | 77 | bind_cols(df, new_fields) 78 | } 79 | -------------------------------------------------------------------------------- /R/add-headline-column.R: -------------------------------------------------------------------------------- 1 | #' Add column of headlines 2 | #' 3 | #' @description This works similar to `headline()` but acts on and returns a 4 | #' data frame. 5 | 6 | #' @return Returns the original data frame with columns appended. 7 | #' 8 | #' @details What is nice about this function is you can return some of the 9 | #' "talking points" used in the headline calculation. For example, if you want 10 | #' to find the most extreme headlines, you can use 11 | #' `add_headline_column(..., return_cols = delta)` This will bring back a 12 | #' `headline` column as well as the `delta` talking point (the absolute 13 | #' difference between `x` and `y`). With this result, you can sort in descending 14 | #' order and filter for the biggest difference. 15 | #' 16 | #' @param df data frame, must be a single row 17 | #' @param .name string value for the name of the new column to create 18 | #' @param return_cols arguments that can be passed to 19 | #' \code{\link[dplyr]{select}}, ex: c("a", "b"), 20 | #' \code{\link[dplyr]{starts_with}}, etc. 21 | #' @inheritParams compare_values 22 | #' @inheritParams headline 23 | #' @export 24 | #' @importFrom glue glue 25 | #' @importFrom dplyr pull mutate transmute select one_of bind_cols rowwise ungroup 26 | #' @importFrom tidyr unnest_wider 27 | #' @importFrom rlang := .data abort warn 28 | #' @importFrom purrr pmap map_dfr flatten 29 | #' @examples 30 | #' 31 | #' # You can use 'add_headline_column()' to reference values in an existing data set. 32 | #' # Here is an example comparing the box office sales of different Pixar films 33 | #' head(pixar_films) |> 34 | #' dplyr::select(film, bo_domestic, bo_intl) |> 35 | #' add_headline_column( 36 | #' x = bo_domestic, 37 | #' y = bo_intl, 38 | #' headline = "{film} was ${delta}M higher {trend} (${x}M vs ${y}M)", 39 | #' trend_phrases = trend_terms(more = "domestically", less = "internationally") 40 | #' ) |> 41 | #' knitr::kable("pandoc") 42 | #' 43 | #' # You can also use 'return_cols' to return any and all "talking points". 44 | #' # You can use tidyselect helpers like 'starts_with("delta")' or 45 | #' # 'everything()'. In this example, I returned the 'raw_delta' & 'trend' columns 46 | #' # and then identified the records at the extremes 47 | #' pixar_films |> 48 | #' dplyr::select(film, bo_domestic, bo_intl) |> 49 | #' add_headline_column( 50 | #' x = bo_domestic, 51 | #' y = bo_intl, 52 | #' headline = "${delta}M {trend} (${x}M vs ${y}M)", 53 | #' trend_phrases = trend_terms(more = "higher", less = "lower"), 54 | #' return_cols = c(raw_delta, trend) 55 | #' ) |> 56 | #' dplyr::filter(raw_delta %in% range(raw_delta)) |> 57 | #' knitr::kable("pandoc") 58 | #' 59 | add_headline_column <- function(df, 60 | x, 61 | y, 62 | headline = "{trend} of {delta} ({orig_values})", 63 | ..., 64 | .name = "headline", 65 | if_match = "There was no difference", 66 | trend_phrases = headliner::trend_terms(), 67 | plural_phrases = NULL, 68 | orig_values = "{x} vs. {y}", 69 | n_decimal = 1, 70 | round_all = TRUE, 71 | multiplier = 1, 72 | return_cols = .name) { 73 | # df <- mtcars; x = as.symbol("gear"); y = as.symbol("carb") 74 | 75 | # inform that headline can be renamed 76 | if (.name %in% names(df)) { 77 | glue( 78 | "The column '{.name}' was replaced. Use the '.name' argument \\ 79 | to change the new column name." 80 | ) |> 81 | warn() 82 | } 83 | 84 | df_vals <- 85 | df |> 86 | mutate(# give unique name in case user passes x = y 87 | use_x = {{x}}, 88 | use_y = {{y}} 89 | ) |> 90 | transmute( 91 | x = .data$use_x, 92 | y = .data$use_y, 93 | headline = {{headline}}, 94 | trend_phrases = list(trend_phrases), 95 | plural_phrases = list(plural_phrases), 96 | orig_values = orig_values, 97 | n_decimal = n_decimal, 98 | round_all = round_all, 99 | multiplier = multiplier, 100 | check_rounding = FALSE # done separately to limit # of warnings 101 | ) 102 | 103 | # check rounding 104 | check_rounding( 105 | pull(df, {{x}}), 106 | pull(df, {{y}}), 107 | n_decimal 108 | ) 109 | 110 | prep_results <- 111 | df_vals |> 112 | select(-.data$headline) |> 113 | pmap(compare_values) |> 114 | map_dfr(flatten) 115 | 116 | headline_results <- 117 | prep_results |> 118 | bind_cols( 119 | df |> 120 | select(-one_of(names(prep_results))) |> 121 | suppressWarnings() 122 | ) |> 123 | mutate(headline = df_vals$headline) |> 124 | rowwise() |> 125 | mutate(headline = glue(.data$headline, ...)) |> 126 | ungroup() |> 127 | mutate( 128 | {{.name}} := 129 | ifelse( 130 | test = .data$x == .data$y, 131 | yes = if_match, 132 | no = .data$headline 133 | ) 134 | ) |> 135 | select({{.name}}, {{return_cols}}) 136 | 137 | bind_cols( 138 | df, 139 | headline_results, 140 | .name_repair = "unique" 141 | ) 142 | } 143 | -------------------------------------------------------------------------------- /R/compare-conditions.R: -------------------------------------------------------------------------------- 1 | #' Compare two conditions within a data frame 2 | #' 3 | #' @description Using logic that \code{\link[dplyr]{filter}} can interpret, 4 | #' `compare_conditions()` will summarize the data aggregating condition `x` and 5 | #' condition `y` 6 | #' 7 | #' @return Returns a data frame that is either 1 row, or if grouped, 8 | #' 1 row per group. 9 | #' 10 | #' @details `compare_conditions()` passes its arguments to 11 | #' \code{\link[dplyr]{across}}. The `.cols` and `.fns` work the same. For 12 | #' clarity, it is helpful to use the \code{\link[dplyr]{lst}} function for the 13 | #' `.fns` parameter. Using 14 | #' `compare_conditions(..., .cols = my_var, .fns = lst(mean, sd))` will return 15 | #' the values `mean_my_var_x`, `mean_my_var_y`, `sd_my_var_x` and `sd_my_var_x` 16 | #' 17 | #' @param df data frame 18 | #' @param x condition for comparison, same criteria you would use in 19 | #' 'dplyr::filter', used in contrast to the reference group 'y' 20 | #' @param y condition for comparison, same criteria you would use in 21 | #' 'dplyr::filter', used in contrast to the reference group 'x' 22 | #' @param .cols columns to use in comparison 23 | #' @param .fns named list of the functions to use, ex: 24 | #' list(avg = mean, sd = sd) 'purrr' style phrases are also supported like 25 | #' list(mean = ~mean(.x, na.rm = TRUE), sd = sd) and dplyr::lst(mean, sd) will 26 | #' create a list(mean = mean, sd = sd) 27 | #' @importFrom dplyr everything lst group_vars group_keys 28 | #' select left_join bind_cols relocate 29 | #' @export 30 | #' 31 | #' @examples 32 | #' 33 | #' # compare_conditions works similar to dplyr::across() 34 | #' pixar_films |> 35 | #' compare_conditions( 36 | #' x = (rating == "G"), 37 | #' y = (rating == "PG"), 38 | #' .cols = rotten_tomatoes 39 | #' ) 40 | #' 41 | #' 42 | #' # because data frames are just fancy lists, you pass the result to headline_list() 43 | #' pixar_films |> 44 | #' compare_conditions( 45 | #' x = (rating == "G"), 46 | #' y = (rating == "PG"), 47 | #' .cols = rotten_tomatoes 48 | #' ) |> 49 | #' headline_list("a difference of {delta} points") 50 | #' 51 | #' 52 | #' # you can return multiple objects to compare 53 | #' # 'view_List()' is a helper to see list objects in a compact way 54 | #' pixar_films |> 55 | #' compare_conditions( 56 | #' x = (rating == "G"), 57 | #' y = (rating == "PG"), 58 | #' .cols = c(rotten_tomatoes, metacritic), 59 | #' .fns = dplyr::lst(mean, sd) 60 | #' ) |> 61 | #' view_list() 62 | #' 63 | #' 64 | #' # you can use any of the `tidyselect` helpers 65 | #' pixar_films |> 66 | #' compare_conditions( 67 | #' x = (rating == "G"), 68 | #' y = (rating == "PG"), 69 | #' .cols = dplyr::starts_with("bo_") 70 | #' ) 71 | #' 72 | #' 73 | #' # if you want to compare x to the overall average, use y = TRUE 74 | #' pixar_films |> 75 | #' compare_conditions( 76 | #' x = (rating == "G"), 77 | #' y = TRUE, 78 | #' .cols = rotten_tomatoes 79 | #' ) 80 | #' 81 | #' 82 | #' # to get the # of observations use length() instead of n() 83 | #' # note: don't pass the parentheses 84 | #' pixar_films |> 85 | #' compare_conditions( 86 | #' x = (rating == "G"), 87 | #' y = (rating == "PG"), 88 | #' .cols = rotten_tomatoes, # can put anything here really 89 | #' .fns = list(n = length) 90 | #' ) 91 | #' 92 | #' 93 | #' # you can also use purrr-style lambdas 94 | #' pixar_films |> 95 | #' compare_conditions( 96 | #' x = (rating == "G"), 97 | #' y = (rating == "PG"), 98 | #' .cols = rotten_tomatoes, 99 | #' .fns = list(avg = ~ sum(.x) / length(.x)) 100 | #' ) 101 | #' 102 | #' # you can compare categorical data with functions like dplyr::n_distinct() 103 | #' pixar_films |> 104 | #' compare_conditions( 105 | #' x = (rating == "G"), 106 | #' y = (rating == "PG"), 107 | #' .cols = film, 108 | #' .fns = list(distinct = dplyr::n_distinct) 109 | #' ) 110 | compare_conditions <- function(df, 111 | x, 112 | y, 113 | .cols = everything(), 114 | .fns = lst(mean) 115 | ) { 116 | # sample inputs for debugging 117 | # df <- pixar_films; .cols <- as.symbol("rotten_tomatoes"); .fns <- lst(mean, sd) 118 | # x <- rlang::new_quosure(rlang::expr(rating == "G")) 119 | # y <- rlang::new_quosure(rlang::expr(TRUE)) 120 | 121 | res_1 <- aggregate_group(df, name = "_x", .cols = {{.cols}}, .fns = .fns, cond = {{x}}) 122 | res_2 <- aggregate_group(df, name = "_y", .cols = {{.cols}}, .fns = .fns, cond = {{y}}) 123 | 124 | # need to account for grouped data frame & reorder vars so they are in order 125 | # ex: mean_x, mean_y, sd_x, sd_y 126 | any_groups <- group_vars(df) 127 | column_order <- 128 | c( 129 | any_groups, 130 | sort(c(names(res_1), names(res_2))) 131 | ) |> 132 | unique() 133 | 134 | 135 | if (length(any_groups)) { # has groups 136 | final <- 137 | group_keys(df) |> 138 | left_join(res_1) |> 139 | left_join(res_2) |> 140 | suppressMessages() # join msg 141 | 142 | } else { # no groups 143 | final <- 144 | res_1 |> 145 | bind_cols(res_2) |> 146 | suppressWarnings() 147 | } 148 | 149 | final |> 150 | relocate(column_order) 151 | } 152 | -------------------------------------------------------------------------------- /R/compare-values.R: -------------------------------------------------------------------------------- 1 | #' Compare two values and get talking points 2 | #' 3 | #' @description A function to create "talking points" that 4 | #' describes the difference between two values. 5 | #' 6 | #' @return `compare_values()` returns a list object that can be used with 7 | #' \code{\link[glue]{glue}} syntax 8 | #' 9 | #' @details 10 | #' Given `compare_values(x = 8, y = 10)` the following items will be returned 11 | #' in the list: 12 | #' 13 | #' |item | value | description | 14 | #' |--- |--- |--- | 15 | #' |`x` | 2 | original `x` value to compare against `y` | 16 | #' |`y` | 10| original `y` value | 17 | #' |`delta` | 8 | absolute difference between `x` & `y` | 18 | #' |`delta_p` | 80| % difference between `x` & `y` | 19 | #' |`article_delta` | "an 8" | `delta` with the article included | 20 | #' |`article_delta_p` | "an 80"| `delta_p` with the article included | 21 | #' |`raw_delta` | -8| true difference between `x` & `y` | 22 | #' |`raw_delta_p` | -80| true % difference between `x` & `y` | 23 | #' |`article_raw_delta` | "a -8" | `raw_delta` with the article | 24 | #' |`article_raw_delta_p` | "a -80"| `raw_delta_p` with the article | 25 | #' |`sign` | -1 | the direction, 1 (increase), -1 (decrease), or 0 (no change) | 26 | #' |`orig_values` | "2 vs. 10"| shorthand for `{x} vs {y}` | 27 | #' |`trend` | "decrease"| influenced by the values in `trend_phrases` argument | 28 | #' 29 | #' 30 | #' @param x a numeric value to compare to the reference value of 'y' 31 | #' @param y a numeric value to act as a control for the 'x' value 32 | #' @param trend_phrases list of values to use for when x is more than y 33 | #' or x is less than y. You can pass it just 34 | #' \code{\link{trend_terms}} (the default) and call the result with 35 | #' \code{"...{trend}..."} or pass is a named list (see examples) 36 | #' @param plural_phrases named list of values to use when difference (delta) is 37 | #' singular (delta = 1) or plural (delta != 1) 38 | #' @param orig_values a string using \code{\link[glue]{glue}} syntax. 39 | #' example: `({x} vs {y})` 40 | #' @param n_decimal numeric value to limit the number of decimal places in 41 | #' the returned values. 42 | #' @param round_all logical value to indicate if all values should be rounded. 43 | #' When FALSE, the values will return with no modification. When TRUE (default) 44 | #' all values will be round to the length specified by 'n_decimal'. 45 | #' @param multiplier number indicating the scaling factor. When multiplier = 1 46 | #' (default), 0.25 will return 0.25. When multiplier = 100, 0.25 will return 25. 47 | #' @param check_rounding when TRUE (default) inputs will be checked to confirm if 48 | #' a difference of zero may be due to rounding. Ex: 0.16 and 0.24 with 49 | #' 'n_decimal = 1' will both return 0.2. Because this will show no difference, 50 | #' a message will be displayed 51 | #' @importFrom glue glue 52 | #' @importFrom purrr map_if 53 | #' @export 54 | #' @md 55 | #' @rdname compare_values 56 | #' @seealso [headline()], [trend_terms()], [plural_phrasing()] and [view_list()] 57 | #' @examples 58 | #' # the values can be manually entered 59 | #' 60 | #' compare_values(10, 8) |> head(2) 61 | #' # percent difference (10-8)/8 62 | #' compare_values(10, 8)$delta_p 63 | #' 64 | #' # trend_phrases returns an object called trend if nothing is passed 65 | #' compare_values(10, 8)$trend 66 | #' 67 | #' # or if one argument is passed using trend_terms() 68 | #' compare_values(10, 8, trend_phrases = trend_terms(more = "higher"))$trend 69 | #' 70 | #' # if a named list is used, the objects are called by their names 71 | #' compare_values( 72 | #' 10, 8, 73 | #' trend_phrases = list( 74 | #' more = trend_terms(), 75 | #' higher = trend_terms("higher", "lower") 76 | #' ) 77 | #' )$higher 78 | #' 79 | #' # a phrase about the comparison can be edited by providing glue syntax 80 | #' # 'c' = the 'compare' value, 'r' = 'reference' 81 | #' compare_values(10, 8, orig_values = "{x} to {y} people")$orig_values 82 | #' 83 | #' # you can also adjust the rounding, although the default is 1 84 | #' compare_values(0.1234, 0.4321)$orig_values 85 | #' compare_values(0.1234, 0.4321, n_decimal = 3)$orig_values 86 | #' # or add a multiplier 87 | #' compare_values(0.1234, 0.4321, multiplier = 100)$orig_values 88 | compare_values <- function(x, 89 | y, 90 | trend_phrases = headliner::trend_terms(), 91 | orig_values = "{x} vs. {y}", 92 | plural_phrases = NULL, 93 | n_decimal = 1, 94 | round_all = TRUE, 95 | multiplier = 1, 96 | check_rounding = TRUE) { 97 | if (length(x) > 1 | length(y) > 1) { 98 | stop( 99 | "compare_values() expects a single value for 'x' and ", 100 | "a single value for 'y'. ", 101 | "If you need to pass multiple inputs, use purrr::map2()" 102 | ) 103 | } 104 | 105 | if (any(is.na(c(x, y)))) { 106 | values <- compare_values(1, 1) # dummy list, need names in headliner 107 | return(map(values, ~NA)) # replace everything with NA & early return 108 | } 109 | # calcs 110 | comp <- (x * multiplier) 111 | ref <- (y * multiplier) 112 | 113 | delta <- as.numeric(comp - ref) 114 | delta_p <- as.numeric(delta / ref * 100) 115 | 116 | calc <- 117 | list( 118 | delta = delta, 119 | delta_p = delta_p, 120 | sign = sign(delta), 121 | abs_delta = abs(delta), 122 | abs_delta_p = abs(delta_p), 123 | x = comp, 124 | y = ref 125 | ) 126 | 127 | if (round_all) { 128 | # give a warning if rounding causes a delta of 0 due to inputs having 129 | # decimals >= n_decimal parameter 130 | if (check_rounding){ 131 | check_rounding( 132 | x = calc$x, 133 | y = calc$y, 134 | n_decimal = n_decimal 135 | ) 136 | } 137 | 138 | calc <- 139 | calc |> 140 | map_if(is.numeric, round, n_decimal) 141 | } 142 | 143 | 144 | trend_terms_list <- return_trend_phrases(trend_phrases, calc$sign) 145 | 146 | output <- 147 | list( 148 | x = calc$x, 149 | y = calc$y, 150 | delta = calc$abs_delta, 151 | delta_p = calc$abs_delta_p, 152 | article_delta = paste(get_article(calc$abs_delta), calc$abs_delta), 153 | article_delta_p = paste(get_article(calc$abs_delta_p), calc$abs_delta_p), 154 | raw_delta = calc$delta, 155 | raw_delta_p = calc$delta_p, 156 | article_raw_delta = paste(get_article(calc$delta), calc$delta), 157 | article_raw_delta_p = paste(get_article(calc$delta_p), calc$delta_p), 158 | sign = calc$sign, 159 | orig_values = glue( 160 | orig_values, 161 | x = calc$x, 162 | y = calc$y 163 | ) 164 | ) 165 | 166 | # apppend trend terms 167 | output <- append(output, trend_terms_list) 168 | 169 | # append plural phrases if provided 170 | if (!is.null(plural_phrases)) { 171 | plural_phrases_list <- 172 | return_plural_phrases(plural_phrases, delta = calc$abs_delta) 173 | 174 | # append to list 175 | output <- append(output, plural_phrases_list) 176 | } 177 | 178 | output 179 | } 180 | 181 | 182 | 183 | #' identify trend terms to use 184 | #' @noRd 185 | #' @importFrom purrr map pluck 186 | #' @examples 187 | #' return_trend_phrases(trend_terms()) 188 | #' return_trend_phrases(list(trend = trend_terms())) 189 | #' return_trend_phrases( 190 | #' list(trend = trend_terms(), x = trend_terms(), y = trend_terms()) 191 | #' ) 192 | return_trend_phrases <- function(trend_phrases, sign = 1) { 193 | # trend_terms() returns a list, this test is: 194 | # FALSE when passed alone 195 | # TRUE when used in a list of trend terms 196 | passed_as_list <- is.list(trend_phrases[[1]]) 197 | 198 | if (passed_as_list) { 199 | trend_list <- trend_phrases 200 | } else { 201 | # else make list 202 | trend_list <- list(trend = trend_phrases) 203 | } 204 | 205 | # early retun if 0 206 | if (sign == 0) { 207 | return( 208 | map(trend_list, pluck, 1) |> 209 | map(~"same") 210 | ) 211 | } 212 | 213 | which_trend <- 214 | switch( 215 | as.character(sign), 216 | "1" = "more", 217 | "-1" = "less" 218 | ) 219 | 220 | map(trend_list, pluck, which_trend) 221 | } 222 | 223 | 224 | #' identify plural phrases to use 225 | #' @noRd 226 | #' @importFrom purrr map pluck 227 | #' @examples 228 | #' return_plural_phrases( 229 | #' plural_phrases = list(x = plural_phrasing("one", "many")) 230 | #' ) 231 | #' return_plural_phrases( 232 | #' list( 233 | #' x = plural_phrasing("one", "many"), 234 | #' y = plural_phrasing("one", "many") 235 | #' ) 236 | #' ) 237 | return_plural_phrases <- function(plural_phrases, delta = 2) { 238 | # stop if items in list aren't named 239 | if (any(is.null(names(plural_phrases)))) { 240 | stop( 241 | "'plural_phrases' should be a named list. \nex. ", 242 | 'list(people = plural_phrasing("person", "people"))', 243 | call. = FALSE 244 | ) 245 | } 246 | 247 | # identify which list element to choose if delta = 1 then single else multi 248 | value <- ifelse(delta == 1, "single", "multi") 249 | 250 | # select correct value 251 | map(plural_phrases, pluck, value) 252 | } 253 | 254 | -------------------------------------------------------------------------------- /R/data.R: -------------------------------------------------------------------------------- 1 | #' This data comes from \href{https://github.com/erictleung/pixarfilms/}{\code{pixarfilms}} 2 | #' package by Eric Leung (2022) 3 | #' 4 | #' The data has box office sales, audience ratings, and release dates for each Pixar film 5 | #' 6 | #' @format A tibble with 22 rows and 10 columns: 7 | #' \describe{ 8 | #' \item{order}{order of release} 9 | #' \item{film}{name of film} 10 | #' \item{release_date}{date film premiered} 11 | #' \item{year}{the year the film premiered} 12 | #' \item{run_time}{film length in minutes} 13 | #' \item{film_rating}{rating based on Motion Picture Association (MPA) film 14 | #' rating system} 15 | #' \item{rotten_tomatoes}{score from the American review-aggregation website 16 | #' Rotten Tomatoes; scored out of 100} 17 | #' \item{metacritic}{score from Metacritic where scores are weighted average 18 | #' of reviews; scored out of 100} 19 | #' \item{bo_domestic}{box office gross amount in U.S. dollars (millions) for 20 | #' U.S. and Canada} 21 | #' \item{bo_intl}{box office gross amount in U.S. dollars (millions) for other 22 | #' territories} 23 | #' } 24 | #' @examples 25 | #' pixar_films 26 | #' 27 | #' library(ggplot2) 28 | #' 29 | #' headline( 30 | #' x = min(pixar_films$run_time), 31 | #' y = max(pixar_films$run_time), 32 | #' headline = 33 | #' "The shortest film was {delta} minutes less than the longest film ({orig_values} minutes)" 34 | #' ) 35 | #' 36 | #' ggplot(pixar_films, aes(bo_intl, rating)) + 37 | #' geom_boxplot() + 38 | #' xlim(0, NA) + 39 | #' labs(title = "International Box Office by MPA Rating") 40 | #' 41 | #' 42 | #' ggplot(pixar_films, aes(release_date, run_time)) + 43 | #' geom_line() + 44 | #' geom_point() + 45 | #' ylim(0, NA) + 46 | #' labs(title = "Film runtimes by release date") 47 | #' 48 | #' 49 | #' ggplot(pixar_films, aes(y = reorder(film, rotten_tomatoes))) + 50 | #' geom_linerange(aes(xmin = rotten_tomatoes, xmax = metacritic), size = 2, color = "grey85") + 51 | #' geom_point(aes(x = rotten_tomatoes, color = "rotten_tomatoes")) + 52 | #' geom_point(aes(x = metacritic, color = "metacritic")) + 53 | #' scale_color_manual(values = c("steelblue1", "coral2")) + 54 | #' theme_minimal(base_size = 9) + 55 | #' labs( 56 | #' title = "Rotten Tomatoes vs Metacritic by film", 57 | #' color = NULL, 58 | #' y = NULL, 59 | #' x = "Audience Score" 60 | #' ) 61 | #' 62 | "pixar_films" 63 | -------------------------------------------------------------------------------- /R/demo-data.R: -------------------------------------------------------------------------------- 1 | #' Small data set referencing the current date 2 | #' @return Returns a data frame of size `n`. 3 | #' @param n number of rows to return 4 | #' @param by string indicating the unit of time between dates in 5 | #' \code{seq.Date(..., by = )} 6 | #' @importFrom tibble tibble 7 | #' @export 8 | #' @examples 9 | #' demo_data() 10 | #' 11 | #' demo_data(n = 8, by = "1 day") 12 | demo_data <- function(n = 10, by = "-2 month") { 13 | tibble( 14 | group = rep_len(rep(letters, each = 2), length.out = n), 15 | sales = 1:n + 100, 16 | count = n:1 + 25, 17 | on_sale = 1:n %% 2, 18 | date = seq.Date(Sys.Date(), length.out = n, by = by) 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /R/headline.R: -------------------------------------------------------------------------------- 1 | #' Compose phrases that describe differences in the data 2 | #' 3 | #' @description Given two values, `headline()` will use 4 | #' \code{\link[glue]{glue}} syntax to string together "talking points". 5 | #' For example `headline(8, 10)` will describe a difference of 2 and can be 6 | #' expressed as 7 | #' `headline(8, 10, headline = "changed by {delta} ({raw_delta_p}%)")`. This 8 | #' returns "changed by 2 (-20%)". 9 | #' 10 | #' @return Returns a character vector the same length as the input, 11 | #' 12 | #' @details `headline()` relies heavily on \code{\link[glue]{glue_data}}. 13 | #' Objects can be combined into a headline using the following search path: 14 | #' If given 15 | #' ```{r} 16 | #' delta <- 123 17 | #' headline(1, 3, delta = "abc") 18 | #' ``` 19 | #' `delta` is one of the "talking points" from `compare_values()` and would 20 | #' usually return "2" but because we passed the named variable 21 | #' `delta = "none"`, `headline()` (really \code{\link[glue]{glue_data}}) 22 | #' will look first at the named variables, then at the result of 23 | #' `compare_values()` then in the global environment. So in the example above, 24 | #' the output will return `"decrease of xxxxxx (1 vs. 3)"` 25 | #' 26 | #' @param headline a string to format the final output. Uses 27 | #' \code{\link[glue]{glue}} syntax 28 | #' @param ... arguments passed to \code{\link[glue]{glue_data}} 29 | #' @param if_match string to display if numbers match, uses 30 | #' \code{\link[glue]{glue}} syntax 31 | #' @param plural_phrases named list of values to use when difference (delta) is 32 | #' singular (delta = 1) or plural (delta != 1) 33 | #' @param n_decimal numeric value to limit the number of decimal places in 34 | #' the returned values. 35 | #' @param round_all logical value to indicate if all values should be rounded. 36 | #' When FALSE, the values will return with no modification. When TRUE (default) 37 | #' all values will be round to the length specified by 'n_decimal'. 38 | #' @param multiplier number indicating the scaling factor. When multiplier = 1 39 | #' (default), 0.25 will return 0.25. When multiplier = 100, 0.25 will return 25. 40 | #' @param return_data logical to indicate whether function should return the 41 | #' talking points used to compose the headline 42 | #' @inheritParams compare_values 43 | #' @importFrom glue glue_data as_glue 44 | #' @importFrom purrr possibly map2_chr map_dbl map2 45 | #' @export 46 | #' @rdname headline 47 | #' @seealso [compare_values()], [trend_terms()], and [add_article()] 48 | #' @examples 49 | #' # values can be manually entered, some headlines are provided by default 50 | #' headline(10, 8) 51 | #' headline(8, 10) 52 | #' headline(1:3, 3:1) 53 | #' 54 | #' # most likely you'll edit the headline by hand 55 | #' headline( 56 | #' x = 10, 57 | #' y = 8, 58 | #' headline = "There was a ${delta} {trend} vs last year" 59 | #' ) 60 | #' 61 | #' # you can also adjust the phrasing of higher/lower values 62 | #' headline( 63 | #' x = 10, 64 | #' y = 8, 65 | #' headline = "Group A was {trend} by {delta_p}%.", 66 | #' trend_phrases = trend_terms(more = "higher", less = "lower") 67 | #' ) 68 | #' 69 | #' # a phrase about the comparion can be edited by providing glue syntax 70 | #' # 'c' = the 'compare' value, 'r' = 'reference' 71 | #' headline(10, 8, orig_values = "{x} to {y} people") 72 | #' 73 | #' # you can also add phrases for when the difference = 1 or not 74 | #' headline( 75 | #' x = 10, 76 | #' y = 8, 77 | #' plural_phrases = list( 78 | #' were = plural_phrasing(single = "was", multi = "were"), 79 | #' people = plural_phrasing(single = "person", multi = "people") 80 | #' ), 81 | #' headline = "there {were} {delta} {people}" 82 | #' ) 83 | #' 84 | #' # you can also adjust the rounding, the default is 1 85 | #' headline(0.1234, 0.4321) 86 | #' headline(0.1234, 0.4321, n_decimal = 3) 87 | #' # or use a multiplier 88 | #' headline(0.1234, 0.4321, multiplier = 100) 89 | #' 90 | #' # there are many components you can assemble 91 | #' headline( 92 | #' x = 16, 93 | #' y = 8, 94 | #' headline = "there was {article_delta_p}% {trend}, \\ 95 | #' {add_article(trend)} of {delta} ({orig_values})" 96 | #' ) 97 | #' 98 | headline <- function(x, 99 | y, 100 | headline = "{trend} of {delta} ({orig_values})", 101 | ..., 102 | if_match = "There was no difference", 103 | trend_phrases = headliner::trend_terms(), 104 | plural_phrases = NULL, 105 | orig_values = "{x} vs. {y}", 106 | n_decimal = 1, 107 | round_all = TRUE, 108 | multiplier = 1, 109 | return_data = FALSE) { 110 | res <- 111 | map2( 112 | .x = x, 113 | .y = y, 114 | .f = 115 | # possibly( 116 | # .f = 117 | ~ compare_values( 118 | .x, 119 | .y, 120 | trend_phrases = trend_phrases, 121 | plural_phrases = plural_phrases, 122 | orig_values = orig_values, 123 | n_decimal = n_decimal, 124 | round_all = round_all, 125 | multiplier = multiplier, 126 | check_rounding = FALSE # will do separately to limit # of warnings 127 | )#,otherwise = NA_character_) 128 | ) 129 | 130 | # check rounding 131 | check_rounding(x, y, n_decimal) 132 | 133 | # determine which headline phrasing to use & pass to glue 134 | use_phrases <- 135 | ifelse( 136 | map_dbl(res, pluck, "sign", .default = NA) == 0, 137 | if_match, 138 | headline 139 | ) 140 | 141 | headlines <- 142 | map2_chr( 143 | .x = res, 144 | .y = use_phrases, 145 | .f = glue_data, ... 146 | # WORKS possibly(glue_data, NA_character_) 147 | ) #|> print() 148 | 149 | 150 | if (return_data) { 151 | full_list <- map2(res, headlines, ~append(list(headline = .y), .x)) 152 | return(full_list) 153 | } 154 | 155 | as_glue(headlines) 156 | } 157 | 158 | 159 | #' @param l a list with values to compare, if named, can call by name 160 | #' @inheritParams headline 161 | #' @rdname headline 162 | #' @export 163 | #' @examples 164 | #' 165 | #' # compare_conditions() produces a one-row data frame that can be 166 | #' # passed to headline_list() 167 | #' pixar_films |> 168 | #' compare_conditions( 169 | #' x = (rating == "G"), 170 | #' y = (rating == "PG"), 171 | #' rotten_tomatoes 172 | #' ) |> 173 | #' headline_list( 174 | #' headline = "On average, G-rated films score {delta} points {trend} than \\ 175 | #' PG films on Rotten Tomatoes", 176 | #' trend_phrases = trend_terms(more = "higher", less = "lower") 177 | #' ) 178 | #' 179 | #' # if you have more than 2 list items, you can specify them by name 180 | #' list( 181 | #' x = 1, 182 | #' y = 2, 183 | #' z = 3 184 | #' ) |> 185 | #' headline_list( 186 | #' x = x, 187 | #' y = z 188 | #' ) 189 | headline_list <- function(l, 190 | headline = "{trend} of {delta} ({orig_values})", 191 | x, 192 | y, 193 | ..., 194 | if_match = "There was no difference.", 195 | trend_phrases = headliner::trend_terms(), 196 | plural_phrases = NULL, 197 | orig_values = "{x} vs. {y}", 198 | n_decimal = 1, 199 | round_all = TRUE, 200 | multiplier = 1, 201 | return_data = FALSE) { 202 | if (missing(x) & missing(y)) { 203 | if (length(l) > 2) { 204 | message( 205 | "Only the first two elements were used. ", 206 | "To specify values, pass a list of only two elements long or ", 207 | "specify using 'x' and 'y'" 208 | ) 209 | } 210 | comp <- l[[1]][1] 211 | ref <- l[[2]][1] 212 | } else { 213 | comp <- l[[deparse(match.call()[["x"]])]] 214 | ref <- l[[deparse(match.call()[["y"]])]] 215 | } 216 | 217 | headline( 218 | x = comp, 219 | y = ref, 220 | headline = headline, 221 | ..., 222 | if_match = if_match, 223 | trend_phrases = trend_phrases, 224 | orig_values = orig_values, 225 | n_decimal = n_decimal, 226 | round_all = round_all, 227 | multiplier = multiplier, 228 | return_data = return_data 229 | ) 230 | } 231 | -------------------------------------------------------------------------------- /R/plural-phrasing.R: -------------------------------------------------------------------------------- 1 | #' List of values to use when change is plural (or singular) 2 | 3 | #' @description `plural_phrasing()` returns a list object describing the value 4 | #' to use when display when `x - y` is 1 (single) or not one (multiple or 5 | #' fraction). This helps write "1 person" vs "2 people" 6 | #' 7 | #' @return Returns a list object. 8 | #' 9 | #' @details `plural_phrasing()` will primarily be used in `headline()` and 10 | #' passed along to `compare_conditions()`. Similar to `trend_terms()`. 11 | #' Plural phrases can be passed in a list. See examples below. 12 | #' 13 | #' @param single string to use when delta = 1 14 | #' @param multi string to use when delta > 1 15 | #' 16 | #' @export 17 | #' 18 | #' @examples 19 | #' plural_phrasing(single = "person", multi = "people") 20 | #' 21 | #' headline( 22 | #' x = 1:2, 23 | #' y = 0, 24 | #' headline = "a difference of {delta} {people}", 25 | #' plural_phrases = list(people = plural_phrasing("person", "people")) 26 | #' ) 27 | #' 28 | #' 29 | #' # a complex example passing multiple trends and plural phrases 30 | #' headline( 31 | #' 35, 30, 32 | #' headline = 33 | #' "We had {an_increase} of {delta} {people}. 34 | #' That is {delta} {more} {employees} \\ 35 | #' than the same time last year ({orig_values}).", 36 | #' trend_phrases = list( 37 | #' an_increase = trend_terms("an increase", "a decrease"), 38 | #' more = trend_terms("more", "less") 39 | #' ), 40 | #' plural_phrases = 41 | #' list( 42 | #' people = plural_phrasing("person", "people"), 43 | #' employees = plural_phrasing("employee", "employees") 44 | #' ) 45 | #' ) 46 | plural_phrasing <- function(single, multi) { 47 | list( 48 | single = single, 49 | multi = multi 50 | ) 51 | } 52 | -------------------------------------------------------------------------------- /R/string-manipulation.R: -------------------------------------------------------------------------------- 1 | #' Append a/an to word 2 | #' 3 | #' @return Returns a vector the same length as the input. 4 | #' 5 | #' @param x string or numeric value 6 | #' 7 | #' @export 8 | #' @md 9 | #' @details 10 | #' This function uses crude logic to append 'a' or 'an' to numbers and phrases. 11 | #' * words that start with aeiou 12 | #' * negative numbers always start with 'a', ex: 'a -3' or 'a -8' 13 | #' * decimals always start with 'a' ex: 0.4 is usually pronounced 14 | #' 'a zero point four' or 'a point four' 15 | #' * numbers starting with 8 are always 'an' 16 | #' * if the integer that comes after thousand or million is 11 or 18 then 'an' 17 | #' * 18,000 becomes 18 and that becomes 'an 18' 18 | #' * if the integer that comes after thousand or million is in 19 | #' 1, 10, 12, 13, 14, 15, 16, 17, 19 then 'a' 20 | #' * 15,500 becomes 15 and that becomes 'a 15' 21 | #' * otherwise 'a' 22 | #' 23 | #' @examples 24 | #' add_article("increase") 25 | #' 26 | #' add_article("decrease") 27 | #' 28 | #' add_article(c(1, 8, 10, 11, 18, 20, 80)) 29 | #' 30 | #' add_article(18123) 31 | #' 32 | #' stats::setNames( 33 | #' add_article(1.8 * 10^(1:7)), 34 | #' prettyNum(1.8 * 10^(1:7), big.mark = ",") 35 | #' ) 36 | #' 37 | add_article <- function(x) { 38 | paste(get_article(x), x) 39 | } 40 | -------------------------------------------------------------------------------- /R/trend-terms.R: -------------------------------------------------------------------------------- 1 | #' Phrases for direction of difference 2 | #' 3 | #' @description `trend_terms()` returns a list object describing the values to 4 | #' display when `x` is greater than `y` or `x` is less than `y`. 5 | #' 6 | #' @return Returns a list object. 7 | #' 8 | #' @details `trend_terms()` will primarily be used in `headline()` and passed 9 | #' along to `compare_conditions()`. Similar to `plural_phrasing()` Trend terms 10 | #' can be passed in a list. See examples below. 11 | #' 12 | #' @param more string to use when x > y 13 | #' @param less string to use when x < y 14 | #' 15 | #' @export 16 | #' @seealso [compare_values()] 17 | #' @examples 18 | #' 19 | #' headline( 20 | #' x = c(9, 11), 21 | #' y = 10, 22 | #' headline = "{trend} by {delta_p}%", 23 | #' trend_phrases = trend_terms("higher", "lower") 24 | #' ) 25 | #' 26 | #' # a complex example passing multiple trends and plural phrases 27 | #' headline( 28 | #' 35, 30, 29 | #' headline = 30 | #' "We had {an_increase} of {delta} {people}. 31 | #' That is {delta} {more} {employees} \\ 32 | #' than the same time last year ({orig_values}).", 33 | #' trend_phrases = list( 34 | #' an_increase = trend_terms("an increase", "a decrease"), 35 | #' more = trend_terms("more", "less") 36 | #' ), 37 | #' plural_phrases = 38 | #' list( 39 | #' people = plural_phrasing("person", "people"), 40 | #' employees = plural_phrasing("employee", "employees") 41 | #' ) 42 | #' ) 43 | trend_terms <- function(more = "increase", 44 | less = "decrease") { 45 | list( 46 | more = more, 47 | less = less 48 | ) 49 | } 50 | -------------------------------------------------------------------------------- /R/utils-data-manipulation.R: -------------------------------------------------------------------------------- 1 | #' Find and warn for overlapping column names 2 | #' 3 | #' @param orig_df data frame 4 | #' @param new_df data frame 5 | #' @param drop when TRUE, columns with same name are dropped from the original 6 | #' and replaced with columns from the function output. 7 | #' @noRd 8 | #' @examples 9 | #' check_overlapping_names(mtcars, mtcars[,1:3]) 10 | check_overlapping_names <- function(orig_df, new_df, drop = FALSE) { 11 | orig_names <- names(orig_df) 12 | new_names <- names(new_df) 13 | overlap <- intersect(new_names, orig_names) 14 | 15 | if (length(overlap) > 0 & !drop) { 16 | warning( 17 | paste( 18 | "This step produced duplicate names and", 19 | "some fields have new names (see above)", 20 | "\nUse 'drop = TRUE' to remove the overlapping columns", 21 | "prior to adding the new date fields" 22 | ), 23 | call. = FALSE 24 | ) 25 | } 26 | 27 | overlap 28 | } 29 | 30 | 31 | #' Rollup data using summarise(across(...)) 32 | #' 33 | #' @param name prefix for 34 | #' @param cond the x or y condition used in compare_conditions() 35 | #' @inheritParams dplyr across 36 | #' @importFrom rlang enquo quo_is_missing 37 | #' @importFrom dplyr filter summarise across 38 | #' @noRd 39 | #' @examples 40 | #' aggregate_group(mtcars, name = "_x", .cols = mpg, .fns = lst(mean, sd)) 41 | aggregate_group <- function(df, name, .cols, .fns, cond) { 42 | # df <- mtcars; name = "_x"; .cols <- as.symbol("mpg"); .fns = mean 43 | # cond <- dplyr::quo(cyl > 4); 44 | 45 | cond <- enquo(cond) 46 | 47 | if (!quo_is_missing(cond)) { 48 | df <- filter(df, {{cond}}) 49 | } 50 | 51 | df |> 52 | summarise( 53 | across({{.cols}}, .fns, .names = "{.fn}_{.col}{name}") 54 | ) 55 | } 56 | 57 | 58 | #' Choose "a" or "an" 59 | #' Definition listed under [add_article()] 60 | #' @param x a number or string 61 | #' @noRd 62 | #' @importFrom dplyr case_when 63 | #' @examples 64 | #' get_article("increase") 65 | #' get_article("decrease") 66 | #' get_article(5) 67 | #' get_article(8) 68 | #' get_article(18123) 69 | #' stats::setNames( 70 | #' get_article(1.8 * 10^(1:7)), 71 | #' prettyNum(1.8 * 10^(1:7), big.mark = ",") 72 | #' ) 73 | #' 74 | get_article <- function(x) { 75 | if (is.character(x)) { 76 | ifelse(grepl("^[aeiou]", tolower(x)), "an", "a") 77 | } else { 78 | x_new <- case_when( 79 | x >= 1e6 ~ x / 1e6, 80 | x >= 1e3 ~ x / 1e3, 81 | x >= 1e2 ~ x / 1e2, 82 | TRUE ~ x 83 | ) |> 84 | floor() 85 | 86 | x_char <- as.character(x_new) 87 | n_char <- nchar(x_char) 88 | 89 | case_when( 90 | # -8, -6, -0.1, 0.123 = a 91 | grepl("^[-0]", x_char) ~ "a", 92 | # 100, 123, 123000 = a 93 | grepl("^1..$", x_char) ~ "a", 94 | # 80 = an 95 | grepl("^8", x_char) ~ "an", 96 | # # 11, 18, 11000, 18000 = an 97 | (grepl("^1[18]", x_char) & n_char %% 2 == 0) ~ "an", 98 | # 1, 10, 12, 13, ... = a 99 | (grepl("^1", x_char) & nchar(x_char) < 3) ~ "a", 100 | # else 101 | TRUE ~ "a" 102 | ) 103 | } 104 | } 105 | 106 | 107 | #' Checks to see if rounding is causing the zero 108 | #' @param x compare value from compare_values() 109 | #' @param y reference value from compare_values() 110 | #' @param n_decimal n_decimal value from compare_values() 111 | #' @importFrom utils head 112 | #' @importFrom glue glue glue_collapse 113 | #' @noRd 114 | #' @examples 115 | #' check_rounding(x = 18:30/100, y = 0.24, n_decimal = 1) 116 | #' check_rounding(x = 0.2, y = 0.24, n_decimal = 2) 117 | check_rounding <- function(x, y, n_decimal) { 118 | rounding_match <- 119 | which( 120 | x != y & 121 | round(x, n_decimal) == round(y, n_decimal) 122 | ) |> 123 | head(3) # only want to show a few examples in message 124 | 125 | n_match <- length(rounding_match) 126 | 127 | # stop if no matches 128 | if (!n_match) { 129 | return() 130 | } 131 | 132 | addl_info <- 133 | if (n_match == 1 & length(c(x, y)) == 2) { 134 | # only one pair submitted 135 | "" 136 | } else if (n_match == 1) { 137 | # only one pair matches 138 | paste0("(see input #", rounding_match, ")") 139 | } else { 140 | # demo list of examples 141 | paste0( 142 | "(ex: input #", 143 | glue_collapse(rounding_match, ", ", last = " and "), 144 | ")" 145 | ) 146 | } 147 | 148 | # else 149 | message( 150 | glue( 151 | "With the rounding applied ('n_decimal = {n_decimal}'), \\ 152 | result may show no change {addl_info} 153 | Consider increasing the 'n_decimal' parameter" 154 | ) 155 | ) 156 | } 157 | -------------------------------------------------------------------------------- /R/utils-dates.R: -------------------------------------------------------------------------------- 1 | #' Calculate the difference between two dates 2 | #' 3 | #' @param from date or date-time vector to be compared against reference date 4 | #' @param unit a character string specifying a unit of time. Valid base units 5 | #' are second, minute, hour, day, week, month, bimonth, quarter, season, 6 | #' halfyear and year. 7 | #' @param to referenme date or date-time (static) for comparison 8 | #' @param n multiple to use in \code{period(n, unit)} 9 | #' @noRd 10 | #' @importFrom lubridate floor_date interval period 11 | #' 12 | #' @examples 13 | #' calc_distance(Sys.Date() + (-3:3), "days", Sys.Date()) 14 | #' calc_distance(Sys.time() + (-3:3) * (86400), "hours", Sys.time()) 15 | calc_distance <- function(from, unit, to = Sys.Date(), n = 1, week_start = 1) { 16 | from_date <- floor_date(from, unit, week_start = week_start) 17 | to_date <- floor_date(to, unit, week_start = week_start) 18 | interval_diff <- interval(to_date, from_date) / period(n, unit) 19 | round(interval_diff) 20 | } 21 | 22 | 23 | #' Add 6 months to the date for fiscal year calculations 24 | #' 25 | #' @param x date 26 | #' @param offset number of months to offset date 27 | #' @importFrom lubridate `%m+%` period 28 | #' @noRd 29 | #' @examples 30 | #' fiscal_date(as.Date("2020-01-01"), offset = 6) 31 | fiscal_date <- function(x, offset) x %m+% period(offset, "months") 32 | 33 | -------------------------------------------------------------------------------- /R/view-list.R: -------------------------------------------------------------------------------- 1 | #' Compact view of list values 2 | #' @return Returns a data frame to display a list or vector vertically. 3 | #' @param x a vector or list to be transposed 4 | #' @export 5 | #' @seealso [compare_values()] 6 | #' @examples 7 | #' compare_values(10, 8) |> 8 | #' view_list() 9 | #' 10 | #' add_article(c(1,8,10, 11, 18)) |> 11 | #' view_list() 12 | view_list <- function(x) { 13 | data.frame( 14 | value = unlist(x) 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | --- 4 | 5 | ```{r, include = FALSE} 6 | knitr::opts_chunk$set( 7 | comment = "#>" 8 | ) 9 | ``` 10 | 11 | 12 | # headliner 13 | 14 | 15 | [![CRAN\_Status\_Badge](https://www.r-pkg.org/badges/version/headliner)](https://cran.r-project.org/package=headliner/) 16 | [![Downloads](https://cranlogs.r-pkg.org/badges/grand-total/headliner)](https://cran.r-project.org/package=headliner) 17 | [![codecov](https://codecov.io/gh/rjake/headliner/branch/main/graph/badge.svg)](https://app.codecov.io/gh/rjake/headliner/) 18 | [![R-CMD-check](https://github.com/rjake/headliner/workflows/R-CMD-check/badge.svg)](https://github.com/rjake/headliner/actions/) 19 | 20 | 21 | 22 | The goal of `headliner` is to translate facts into insights. Given two values, `headliner` generates building blocks for creating dynamic text. These talking points can be combined using using `glue` syntax to add informative titles to plots, section headers or other text in a report. 23 | 24 | ## Installation 25 | 26 | You can install the dev version of `headliner` from [github](https://github.com/rjake/headliner) with: 27 | 28 | ``` r 29 | devtools::install_github("rjake/headliner") 30 | ``` 31 | 32 | Let's look at some of the talking points for the difference between 5 and 7: 33 | ```{r} 34 | library(headliner) 35 | 36 | compare_values(5, 7) |> # returns a list 37 | view_list() # show as a data frame 38 | ``` 39 | 40 | We can string the talking points together like this: 41 | 42 | ```{r} 43 | headline( 44 | x = 5, 45 | y = 7, 46 | headline = "There was {article_delta_p}% {trend} ({orig_values})" 47 | ) 48 | ``` 49 | 50 | 51 | See [here](https://rjake.github.io/headliner/articles/intro.html) for a longer introduction. 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # headliner 5 | 6 | 7 | 8 | [![CRAN_Status_Badge](https://www.r-pkg.org/badges/version/headliner)](https://cran.r-project.org/package=headliner/) 9 | [![Downloads](https://cranlogs.r-pkg.org/badges/grand-total/headliner)](https://cran.r-project.org/package=headliner) 10 | [![codecov](https://codecov.io/gh/rjake/headliner/branch/main/graph/badge.svg)](https://app.codecov.io/gh/rjake/headliner/) 11 | [![R-CMD-check](https://github.com/rjake/headliner/workflows/R-CMD-check/badge.svg)](https://github.com/rjake/headliner/actions/) 12 | 13 | 14 | 15 | The goal of `headliner` is to translate facts into insights. Given two 16 | values, `headliner` generates building blocks for creating dynamic text. 17 | These talking points can be combined using using `glue` syntax to add 18 | informative titles to plots, section headers or other text in a report. 19 | 20 | ## Installation 21 | 22 | You can install the dev version of `headliner` from 23 | [github](https://github.com/rjake/headliner) with: 24 | 25 | ``` r 26 | devtools::install_github("rjake/headliner") 27 | ``` 28 | 29 | Let’s look at some of the talking points for the difference between 5 30 | and 7: 31 | 32 | ``` r 33 | library(headliner) 34 | 35 | compare_values(5, 7) |> # returns a list 36 | view_list() # show as a data frame 37 | ``` 38 | 39 | #> value 40 | #> x 5 41 | #> y 7 42 | #> delta 2 43 | #> delta_p 28.6 44 | #> article_delta a 2 45 | #> article_delta_p a 28.6 46 | #> raw_delta -2 47 | #> raw_delta_p -28.6 48 | #> article_raw_delta a -2 49 | #> article_raw_delta_p a -28.6 50 | #> sign -1 51 | #> orig_values 5 vs. 7 52 | #> trend decrease 53 | 54 | We can string the talking points together like this: 55 | 56 | ``` r 57 | headline( 58 | x = 5, 59 | y = 7, 60 | headline = "There was {article_delta_p}% {trend} ({orig_values})" 61 | ) 62 | ``` 63 | 64 | #> There was a 28.6% decrease (5 vs. 7) 65 | 66 | See [here](https://rjake.github.io/headliner/articles/intro.html) for a 67 | longer introduction. 68 | -------------------------------------------------------------------------------- /_pkgdown.yml: -------------------------------------------------------------------------------- 1 | template: 2 | bootstrap: 5 3 | 4 | reference: 5 | 6 | - title: Phrases & Talking Points 7 | desc: > 8 | Along with `headline()`, these functions allow you to expand and 9 | view talking points. 10 | contents: 11 | - headline 12 | - plural_phrasing 13 | - trend_terms 14 | - add_headline_column 15 | - compare_values 16 | - view_list 17 | - title: Data Prep Helpers 18 | desc: > 19 | There are some helper functions to prepare data frames for use 20 | with `headline()` 21 | contents: 22 | - compare_conditions 23 | - add_date_columns 24 | - title: String Manipulation 25 | desc: > 26 | Helper functions to edit strings used within headlines 27 | contents: 28 | - add_article 29 | - title: Data 30 | desc: > 31 | `headliner` comes with two datasets that are used in examples 32 | and vignettes 33 | contents: 34 | - demo_data 35 | - pixar_films 36 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | target: 100% 6 | comment: 7 | layout: "diff, files" 8 | -------------------------------------------------------------------------------- /cran-comments.md: -------------------------------------------------------------------------------- 1 | ## Test environments 2 | * local 3 | * Windows 11, R 4.2.2 4 | * R devel via devtools::check_win_devel() 5 | * github-actions 6 | * macOS (release) 7 | * ubuntu (release, dev) 8 | * windows (release) 9 | 10 | ## R CMD check results 11 | 12 | 0 errors | 0 warnings | 0 notes 13 | 14 | * Applying patch for upcoming dplyr release 15 | * Fixed issue with links not starting 'https' 16 | -------------------------------------------------------------------------------- /data-raw/pixar.R: -------------------------------------------------------------------------------- 1 | library(pixarfilms) 2 | library(tidyverse) 3 | library(lubridate) 4 | 5 | pixar_films <- 6 | pixarfilms::pixar_films |> 7 | transmute( 8 | order = as.integer(number), 9 | film, 10 | release_date, 11 | year = year(release_date), 12 | rating = film_rating, 13 | run_time 14 | ) |> 15 | left_join( 16 | pixarfilms::public_response |> 17 | select( 18 | film, 19 | rotten_tomatoes, 20 | metacritic)) |> 21 | left_join( 22 | pixarfilms::box_office |> #slice(1) |> t() 23 | transmute( 24 | film, 25 | #budget_m = round(budget / 1e6, 1), 26 | bo_domestic = round(box_office_us_canada / 1e6, 1), 27 | bo_intl = round(box_office_other / 1e6, 1) 28 | ) 29 | ) |> 30 | drop_na() |> 31 | print(n = 5) 32 | 33 | usethis::use_data(pixar_films, overwrite = TRUE) 34 | 35 | -------------------------------------------------------------------------------- /data/pixar_films.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjake/headliner/efe92de137bf522160c89c603ee180088405bbc3/data/pixar_films.rda -------------------------------------------------------------------------------- /docs/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Page not found (404) • headliner 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 25 | 26 | 27 | Skip to contents 28 | 29 | 30 |
73 |
74 |
78 | 79 | Content not found. Please use links in the navbar. 80 | 81 |
82 |
83 | 84 | 85 |
89 | 90 | 94 | 95 |
96 |
97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /docs/LICENSE-text.html: -------------------------------------------------------------------------------- 1 | 2 | License • headliner 6 | Skip to contents 7 | 8 | 9 |
47 |
48 |
52 | 53 |
YEAR: 2020
54 | COPYRIGHT HOLDER: Jake Riley
55 | 
56 | 57 |
58 | 59 | 60 |
63 | 64 | 67 | 68 |
69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /docs/LICENSE.html: -------------------------------------------------------------------------------- 1 | 2 | MIT License • headliner 6 | Skip to contents 7 | 8 | 9 |
47 |
48 |
52 | 53 |
54 | 55 |

Copyright (c) 2020 Jake Riley

56 |

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:

57 |

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

58 |

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.

59 |
60 | 61 |
62 | 63 | 64 |
67 | 68 | 71 | 72 |
73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /docs/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjake/headliner/efe92de137bf522160c89c603ee180088405bbc3/docs/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /docs/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjake/headliner/efe92de137bf522160c89c603ee180088405bbc3/docs/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /docs/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjake/headliner/efe92de137bf522160c89c603ee180088405bbc3/docs/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /docs/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjake/headliner/efe92de137bf522160c89c603ee180088405bbc3/docs/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /docs/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjake/headliner/efe92de137bf522160c89c603ee180088405bbc3/docs/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /docs/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjake/headliner/efe92de137bf522160c89c603ee180088405bbc3/docs/apple-touch-icon.png -------------------------------------------------------------------------------- /docs/articles/index.html: -------------------------------------------------------------------------------- 1 | 2 | Articles • headliner 6 | Skip to contents 7 | 8 | 9 |
47 |
48 |
51 | 52 |
53 |

All vignettes

54 |

55 | 56 |
Introduction to headliner
57 |
58 |
59 |
60 | 61 | 62 |
65 | 66 | 69 | 70 |
71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /docs/authors.html: -------------------------------------------------------------------------------- 1 | 2 | Authors and Citation • headliner 6 | Skip to contents 7 | 8 | 9 |
47 |
48 |
51 | 52 |
53 |

Authors

54 | 55 |
  • 56 |

    Jake Riley. Author, maintainer. 57 |

    58 |
  • 59 |
60 | 61 |
62 |

Citation

63 |

Source: DESCRIPTION

64 | 65 |

Riley J (2022). 66 | headliner: Compose Sentences to Describe Comparisons. 67 | https://rjake.github.io/headliner/, https://github.com/rjake/headliner/. 68 |

69 |
@Manual{,
70 |   title = {headliner: Compose Sentences to Describe Comparisons},
71 |   author = {Jake Riley},
72 |   year = {2022},
73 |   note = {https://rjake.github.io/headliner/, https://github.com/rjake/headliner/},
74 | }
75 |
76 |
78 | 79 | 80 |
83 | 84 | 87 | 88 |
89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /docs/bootstrap-toc.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Table of Contents v0.4.1 (http://afeld.github.io/bootstrap-toc/) 3 | * Copyright 2015 Aidan Feldman 4 | * Licensed under MIT (https://github.com/afeld/bootstrap-toc/blob/gh-pages/LICENSE.md) */ 5 | 6 | /* modified from https://github.com/twbs/bootstrap/blob/94b4076dd2efba9af71f0b18d4ee4b163aa9e0dd/docs/assets/css/src/docs.css#L548-L601 */ 7 | 8 | /* All levels of nav */ 9 | nav[data-toggle='toc'] .nav > li > a { 10 | display: block; 11 | padding: 4px 20px; 12 | font-size: 13px; 13 | font-weight: 500; 14 | color: #767676; 15 | } 16 | nav[data-toggle='toc'] .nav > li > a:hover, 17 | nav[data-toggle='toc'] .nav > li > a:focus { 18 | padding-left: 19px; 19 | color: #563d7c; 20 | text-decoration: none; 21 | background-color: transparent; 22 | border-left: 1px solid #563d7c; 23 | } 24 | nav[data-toggle='toc'] .nav > .active > a, 25 | nav[data-toggle='toc'] .nav > .active:hover > a, 26 | nav[data-toggle='toc'] .nav > .active:focus > a { 27 | padding-left: 18px; 28 | font-weight: bold; 29 | color: #563d7c; 30 | background-color: transparent; 31 | border-left: 2px solid #563d7c; 32 | } 33 | 34 | /* Nav: second level (shown on .active) */ 35 | nav[data-toggle='toc'] .nav .nav { 36 | display: none; /* Hide by default, but at >768px, show it */ 37 | padding-bottom: 10px; 38 | } 39 | nav[data-toggle='toc'] .nav .nav > li > a { 40 | padding-top: 1px; 41 | padding-bottom: 1px; 42 | padding-left: 30px; 43 | font-size: 12px; 44 | font-weight: normal; 45 | } 46 | nav[data-toggle='toc'] .nav .nav > li > a:hover, 47 | nav[data-toggle='toc'] .nav .nav > li > a:focus { 48 | padding-left: 29px; 49 | } 50 | nav[data-toggle='toc'] .nav .nav > .active > a, 51 | nav[data-toggle='toc'] .nav .nav > .active:hover > a, 52 | nav[data-toggle='toc'] .nav .nav > .active:focus > a { 53 | padding-left: 28px; 54 | font-weight: 500; 55 | } 56 | 57 | /* from https://github.com/twbs/bootstrap/blob/e38f066d8c203c3e032da0ff23cd2d6098ee2dd6/docs/assets/css/src/docs.css#L631-L634 */ 58 | nav[data-toggle='toc'] .nav > .active > ul { 59 | display: block; 60 | } 61 | -------------------------------------------------------------------------------- /docs/bootstrap-toc.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Table of Contents v0.4.1 (http://afeld.github.io/bootstrap-toc/) 3 | * Copyright 2015 Aidan Feldman 4 | * Licensed under MIT (https://github.com/afeld/bootstrap-toc/blob/gh-pages/LICENSE.md) */ 5 | (function() { 6 | 'use strict'; 7 | 8 | window.Toc = { 9 | helpers: { 10 | // return all matching elements in the set, or their descendants 11 | findOrFilter: function($el, selector) { 12 | // http://danielnouri.org/notes/2011/03/14/a-jquery-find-that-also-finds-the-root-element/ 13 | // http://stackoverflow.com/a/12731439/358804 14 | var $descendants = $el.find(selector); 15 | return $el.filter(selector).add($descendants).filter(':not([data-toc-skip])'); 16 | }, 17 | 18 | generateUniqueIdBase: function(el) { 19 | var text = $(el).text(); 20 | var anchor = text.trim().toLowerCase().replace(/[^A-Za-z0-9]+/g, '-'); 21 | return anchor || el.tagName.toLowerCase(); 22 | }, 23 | 24 | generateUniqueId: function(el) { 25 | var anchorBase = this.generateUniqueIdBase(el); 26 | for (var i = 0; ; i++) { 27 | var anchor = anchorBase; 28 | if (i > 0) { 29 | // add suffix 30 | anchor += '-' + i; 31 | } 32 | // check if ID already exists 33 | if (!document.getElementById(anchor)) { 34 | return anchor; 35 | } 36 | } 37 | }, 38 | 39 | generateAnchor: function(el) { 40 | if (el.id) { 41 | return el.id; 42 | } else { 43 | var anchor = this.generateUniqueId(el); 44 | el.id = anchor; 45 | return anchor; 46 | } 47 | }, 48 | 49 | createNavList: function() { 50 | return $(''); 51 | }, 52 | 53 | createChildNavList: function($parent) { 54 | var $childList = this.createNavList(); 55 | $parent.append($childList); 56 | return $childList; 57 | }, 58 | 59 | generateNavEl: function(anchor, text) { 60 | var $a = $(''); 61 | $a.attr('href', '#' + anchor); 62 | $a.text(text); 63 | var $li = $('
  • '); 64 | $li.append($a); 65 | return $li; 66 | }, 67 | 68 | generateNavItem: function(headingEl) { 69 | var anchor = this.generateAnchor(headingEl); 70 | var $heading = $(headingEl); 71 | var text = $heading.data('toc-text') || $heading.text(); 72 | return this.generateNavEl(anchor, text); 73 | }, 74 | 75 | // Find the first heading level (`

    `, then `

    `, etc.) that has more than one element. Defaults to 1 (for `

    `). 76 | getTopLevel: function($scope) { 77 | for (var i = 1; i <= 6; i++) { 78 | var $headings = this.findOrFilter($scope, 'h' + i); 79 | if ($headings.length > 1) { 80 | return i; 81 | } 82 | } 83 | 84 | return 1; 85 | }, 86 | 87 | // returns the elements for the top level, and the next below it 88 | getHeadings: function($scope, topLevel) { 89 | var topSelector = 'h' + topLevel; 90 | 91 | var secondaryLevel = topLevel + 1; 92 | var secondarySelector = 'h' + secondaryLevel; 93 | 94 | return this.findOrFilter($scope, topSelector + ',' + secondarySelector); 95 | }, 96 | 97 | getNavLevel: function(el) { 98 | return parseInt(el.tagName.charAt(1), 10); 99 | }, 100 | 101 | populateNav: function($topContext, topLevel, $headings) { 102 | var $context = $topContext; 103 | var $prevNav; 104 | 105 | var helpers = this; 106 | $headings.each(function(i, el) { 107 | var $newNav = helpers.generateNavItem(el); 108 | var navLevel = helpers.getNavLevel(el); 109 | 110 | // determine the proper $context 111 | if (navLevel === topLevel) { 112 | // use top level 113 | $context = $topContext; 114 | } else if ($prevNav && $context === $topContext) { 115 | // create a new level of the tree and switch to it 116 | $context = helpers.createChildNavList($prevNav); 117 | } // else use the current $context 118 | 119 | $context.append($newNav); 120 | 121 | $prevNav = $newNav; 122 | }); 123 | }, 124 | 125 | parseOps: function(arg) { 126 | var opts; 127 | if (arg.jquery) { 128 | opts = { 129 | $nav: arg 130 | }; 131 | } else { 132 | opts = arg; 133 | } 134 | opts.$scope = opts.$scope || $(document.body); 135 | return opts; 136 | } 137 | }, 138 | 139 | // accepts a jQuery object, or an options object 140 | init: function(opts) { 141 | opts = this.helpers.parseOps(opts); 142 | 143 | // ensure that the data attribute is in place for styling 144 | opts.$nav.attr('data-toggle', 'toc'); 145 | 146 | var $topContext = this.helpers.createChildNavList(opts.$nav); 147 | var topLevel = this.helpers.getTopLevel(opts.$scope); 148 | var $headings = this.helpers.getHeadings(opts.$scope, topLevel); 149 | this.helpers.populateNav($topContext, topLevel, $headings); 150 | } 151 | }; 152 | 153 | $(function() { 154 | $('nav[data-toggle="toc"]').each(function(i, el) { 155 | var $nav = $(el); 156 | Toc.init($nav); 157 | }); 158 | }); 159 | })(); 160 | -------------------------------------------------------------------------------- /docs/deps/data-deps.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/docsearch.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 3 | // register a handler to move the focus to the search bar 4 | // upon pressing shift + "/" (i.e. "?") 5 | $(document).on('keydown', function(e) { 6 | if (e.shiftKey && e.keyCode == 191) { 7 | e.preventDefault(); 8 | $("#search-input").focus(); 9 | } 10 | }); 11 | 12 | $(document).ready(function() { 13 | // do keyword highlighting 14 | /* modified from https://jsfiddle.net/julmot/bL6bb5oo/ */ 15 | var mark = function() { 16 | 17 | var referrer = document.URL ; 18 | var paramKey = "q" ; 19 | 20 | if (referrer.indexOf("?") !== -1) { 21 | var qs = referrer.substr(referrer.indexOf('?') + 1); 22 | var qs_noanchor = qs.split('#')[0]; 23 | var qsa = qs_noanchor.split('&'); 24 | var keyword = ""; 25 | 26 | for (var i = 0; i < qsa.length; i++) { 27 | var currentParam = qsa[i].split('='); 28 | 29 | if (currentParam.length !== 2) { 30 | continue; 31 | } 32 | 33 | if (currentParam[0] == paramKey) { 34 | keyword = decodeURIComponent(currentParam[1].replace(/\+/g, "%20")); 35 | } 36 | } 37 | 38 | if (keyword !== "") { 39 | $(".contents").unmark({ 40 | done: function() { 41 | $(".contents").mark(keyword); 42 | } 43 | }); 44 | } 45 | } 46 | }; 47 | 48 | mark(); 49 | }); 50 | }); 51 | 52 | /* Search term highlighting ------------------------------*/ 53 | 54 | function matchedWords(hit) { 55 | var words = []; 56 | 57 | var hierarchy = hit._highlightResult.hierarchy; 58 | // loop to fetch from lvl0, lvl1, etc. 59 | for (var idx in hierarchy) { 60 | words = words.concat(hierarchy[idx].matchedWords); 61 | } 62 | 63 | var content = hit._highlightResult.content; 64 | if (content) { 65 | words = words.concat(content.matchedWords); 66 | } 67 | 68 | // return unique words 69 | var words_uniq = [...new Set(words)]; 70 | return words_uniq; 71 | } 72 | 73 | function updateHitURL(hit) { 74 | 75 | var words = matchedWords(hit); 76 | var url = ""; 77 | 78 | if (hit.anchor) { 79 | url = hit.url_without_anchor + '?q=' + escape(words.join(" ")) + '#' + hit.anchor; 80 | } else { 81 | url = hit.url + '?q=' + escape(words.join(" ")); 82 | } 83 | 84 | return url; 85 | } 86 | -------------------------------------------------------------------------------- /docs/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjake/headliner/efe92de137bf522160c89c603ee180088405bbc3/docs/favicon-16x16.png -------------------------------------------------------------------------------- /docs/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjake/headliner/efe92de137bf522160c89c603ee180088405bbc3/docs/favicon-32x32.png -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjake/headliner/efe92de137bf522160c89c603ee180088405bbc3/docs/favicon.ico -------------------------------------------------------------------------------- /docs/link.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /docs/news/index.html: -------------------------------------------------------------------------------- 1 | 2 | Changelog • headliner 6 | Skip to contents 7 | 8 | 9 |
    47 |
    48 |
    52 | 53 |
    54 |

    headliner 0.0.3

    55 |
    • Patch fix ahead of dplyr 1.1.0 (#121)
    • 56 |
    • Reformat NEWS.md headers (#117)
    • 57 |
    58 |
    59 |

    headliner 0.0.2

    CRAN release: 2022-06-26

    60 |
    61 |

    New Features

    62 |
    65 |
    66 |

    Bug fixes

    67 |
    72 |
    73 |

    Other

    74 |
    • 75 | "same" argument removed from trend_terms() as it is not being utilized (#115)
    • 76 |
    77 |
    78 |
    79 |

    headliner 0.0.1

    CRAN release: 2022-06-02

    80 |
    • Initial release
    • 81 |
    82 |
    84 | 85 | 86 |
    89 | 90 | 93 | 94 |
    95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /docs/pkgdown.css: -------------------------------------------------------------------------------- 1 | /* Sticky footer */ 2 | 3 | /** 4 | * Basic idea: https://philipwalton.github.io/solved-by-flexbox/demos/sticky-footer/ 5 | * Details: https://github.com/philipwalton/solved-by-flexbox/blob/master/assets/css/components/site.css 6 | * 7 | * .Site -> body > .container 8 | * .Site-content -> body > .container .row 9 | * .footer -> footer 10 | * 11 | * Key idea seems to be to ensure that .container and __all its parents__ 12 | * have height set to 100% 13 | * 14 | */ 15 | 16 | html, body { 17 | height: 100%; 18 | } 19 | 20 | body { 21 | position: relative; 22 | } 23 | 24 | body > .container { 25 | display: flex; 26 | height: 100%; 27 | flex-direction: column; 28 | } 29 | 30 | body > .container .row { 31 | flex: 1 0 auto; 32 | } 33 | 34 | footer { 35 | margin-top: 45px; 36 | padding: 35px 0 36px; 37 | border-top: 1px solid #e5e5e5; 38 | color: #666; 39 | display: flex; 40 | flex-shrink: 0; 41 | } 42 | footer p { 43 | margin-bottom: 0; 44 | } 45 | footer div { 46 | flex: 1; 47 | } 48 | footer .pkgdown { 49 | text-align: right; 50 | } 51 | footer p { 52 | margin-bottom: 0; 53 | } 54 | 55 | img.icon { 56 | float: right; 57 | } 58 | 59 | /* Ensure in-page images don't run outside their container */ 60 | .contents img { 61 | max-width: 100%; 62 | height: auto; 63 | } 64 | 65 | /* Fix bug in bootstrap (only seen in firefox) */ 66 | summary { 67 | display: list-item; 68 | } 69 | 70 | /* Typographic tweaking ---------------------------------*/ 71 | 72 | .contents .page-header { 73 | margin-top: calc(-60px + 1em); 74 | } 75 | 76 | dd { 77 | margin-left: 3em; 78 | } 79 | 80 | /* Section anchors ---------------------------------*/ 81 | 82 | a.anchor { 83 | display: none; 84 | margin-left: 5px; 85 | width: 20px; 86 | height: 20px; 87 | 88 | background-image: url(./link.svg); 89 | background-repeat: no-repeat; 90 | background-size: 20px 20px; 91 | background-position: center center; 92 | } 93 | 94 | h1:hover .anchor, 95 | h2:hover .anchor, 96 | h3:hover .anchor, 97 | h4:hover .anchor, 98 | h5:hover .anchor, 99 | h6:hover .anchor { 100 | display: inline-block; 101 | } 102 | 103 | /* Fixes for fixed navbar --------------------------*/ 104 | 105 | .contents h1, .contents h2, .contents h3, .contents h4 { 106 | padding-top: 60px; 107 | margin-top: -40px; 108 | } 109 | 110 | /* Navbar submenu --------------------------*/ 111 | 112 | .dropdown-submenu { 113 | position: relative; 114 | } 115 | 116 | .dropdown-submenu>.dropdown-menu { 117 | top: 0; 118 | left: 100%; 119 | margin-top: -6px; 120 | margin-left: -1px; 121 | border-radius: 0 6px 6px 6px; 122 | } 123 | 124 | .dropdown-submenu:hover>.dropdown-menu { 125 | display: block; 126 | } 127 | 128 | .dropdown-submenu>a:after { 129 | display: block; 130 | content: " "; 131 | float: right; 132 | width: 0; 133 | height: 0; 134 | border-color: transparent; 135 | border-style: solid; 136 | border-width: 5px 0 5px 5px; 137 | border-left-color: #cccccc; 138 | margin-top: 5px; 139 | margin-right: -10px; 140 | } 141 | 142 | .dropdown-submenu:hover>a:after { 143 | border-left-color: #ffffff; 144 | } 145 | 146 | .dropdown-submenu.pull-left { 147 | float: none; 148 | } 149 | 150 | .dropdown-submenu.pull-left>.dropdown-menu { 151 | left: -100%; 152 | margin-left: 10px; 153 | border-radius: 6px 0 6px 6px; 154 | } 155 | 156 | /* Sidebar --------------------------*/ 157 | 158 | #pkgdown-sidebar { 159 | margin-top: 30px; 160 | position: -webkit-sticky; 161 | position: sticky; 162 | top: 70px; 163 | } 164 | 165 | #pkgdown-sidebar h2 { 166 | font-size: 1.5em; 167 | margin-top: 1em; 168 | } 169 | 170 | #pkgdown-sidebar h2:first-child { 171 | margin-top: 0; 172 | } 173 | 174 | #pkgdown-sidebar .list-unstyled li { 175 | margin-bottom: 0.5em; 176 | } 177 | 178 | /* bootstrap-toc tweaks ------------------------------------------------------*/ 179 | 180 | /* All levels of nav */ 181 | 182 | nav[data-toggle='toc'] .nav > li > a { 183 | padding: 4px 20px 4px 6px; 184 | font-size: 1.5rem; 185 | font-weight: 400; 186 | color: inherit; 187 | } 188 | 189 | nav[data-toggle='toc'] .nav > li > a:hover, 190 | nav[data-toggle='toc'] .nav > li > a:focus { 191 | padding-left: 5px; 192 | color: inherit; 193 | border-left: 1px solid #878787; 194 | } 195 | 196 | nav[data-toggle='toc'] .nav > .active > a, 197 | nav[data-toggle='toc'] .nav > .active:hover > a, 198 | nav[data-toggle='toc'] .nav > .active:focus > a { 199 | padding-left: 5px; 200 | font-size: 1.5rem; 201 | font-weight: 400; 202 | color: inherit; 203 | border-left: 2px solid #878787; 204 | } 205 | 206 | /* Nav: second level (shown on .active) */ 207 | 208 | nav[data-toggle='toc'] .nav .nav { 209 | display: none; /* Hide by default, but at >768px, show it */ 210 | padding-bottom: 10px; 211 | } 212 | 213 | nav[data-toggle='toc'] .nav .nav > li > a { 214 | padding-left: 16px; 215 | font-size: 1.35rem; 216 | } 217 | 218 | nav[data-toggle='toc'] .nav .nav > li > a:hover, 219 | nav[data-toggle='toc'] .nav .nav > li > a:focus { 220 | padding-left: 15px; 221 | } 222 | 223 | nav[data-toggle='toc'] .nav .nav > .active > a, 224 | nav[data-toggle='toc'] .nav .nav > .active:hover > a, 225 | nav[data-toggle='toc'] .nav .nav > .active:focus > a { 226 | padding-left: 15px; 227 | font-weight: 500; 228 | font-size: 1.35rem; 229 | } 230 | 231 | /* orcid ------------------------------------------------------------------- */ 232 | 233 | .orcid { 234 | font-size: 16px; 235 | color: #A6CE39; 236 | /* margins are required by official ORCID trademark and display guidelines */ 237 | margin-left:4px; 238 | margin-right:4px; 239 | vertical-align: middle; 240 | } 241 | 242 | /* Reference index & topics ----------------------------------------------- */ 243 | 244 | .ref-index th {font-weight: normal;} 245 | 246 | .ref-index td {vertical-align: top; min-width: 100px} 247 | .ref-index .icon {width: 40px;} 248 | .ref-index .alias {width: 40%;} 249 | .ref-index-icons .alias {width: calc(40% - 40px);} 250 | .ref-index .title {width: 60%;} 251 | 252 | .ref-arguments th {text-align: right; padding-right: 10px;} 253 | .ref-arguments th, .ref-arguments td {vertical-align: top; min-width: 100px} 254 | .ref-arguments .name {width: 20%;} 255 | .ref-arguments .desc {width: 80%;} 256 | 257 | /* Nice scrolling for wide elements --------------------------------------- */ 258 | 259 | table { 260 | display: block; 261 | overflow: auto; 262 | } 263 | 264 | /* Syntax highlighting ---------------------------------------------------- */ 265 | 266 | pre, code, pre code { 267 | background-color: #f8f8f8; 268 | color: #333; 269 | } 270 | pre, pre code { 271 | white-space: pre-wrap; 272 | word-break: break-all; 273 | overflow-wrap: break-word; 274 | } 275 | 276 | pre { 277 | border: 1px solid #eee; 278 | } 279 | 280 | pre .img, pre .r-plt { 281 | margin: 5px 0; 282 | } 283 | 284 | pre .img img, pre .r-plt img { 285 | background-color: #fff; 286 | } 287 | 288 | code a, pre a { 289 | color: #375f84; 290 | } 291 | 292 | a.sourceLine:hover { 293 | text-decoration: none; 294 | } 295 | 296 | .fl {color: #1514b5;} 297 | .fu {color: #000000;} /* function */ 298 | .ch,.st {color: #036a07;} /* string */ 299 | .kw {color: #264D66;} /* keyword */ 300 | .co {color: #888888;} /* comment */ 301 | 302 | .error {font-weight: bolder;} 303 | .warning {font-weight: bolder;} 304 | 305 | /* Clipboard --------------------------*/ 306 | 307 | .hasCopyButton { 308 | position: relative; 309 | } 310 | 311 | .btn-copy-ex { 312 | position: absolute; 313 | right: 0; 314 | top: 0; 315 | visibility: hidden; 316 | } 317 | 318 | .hasCopyButton:hover button.btn-copy-ex { 319 | visibility: visible; 320 | } 321 | 322 | /* headroom.js ------------------------ */ 323 | 324 | .headroom { 325 | will-change: transform; 326 | transition: transform 200ms linear; 327 | } 328 | .headroom--pinned { 329 | transform: translateY(0%); 330 | } 331 | .headroom--unpinned { 332 | transform: translateY(-100%); 333 | } 334 | 335 | /* mark.js ----------------------------*/ 336 | 337 | mark { 338 | background-color: rgba(255, 255, 51, 0.5); 339 | border-bottom: 2px solid rgba(255, 153, 51, 0.3); 340 | padding: 1px; 341 | } 342 | 343 | /* vertical spacing after htmlwidgets */ 344 | .html-widget { 345 | margin-bottom: 10px; 346 | } 347 | 348 | /* fontawesome ------------------------ */ 349 | 350 | .fab { 351 | font-family: "Font Awesome 5 Brands" !important; 352 | } 353 | 354 | /* don't display links in code chunks when printing */ 355 | /* source: https://stackoverflow.com/a/10781533 */ 356 | @media print { 357 | code a:link:after, code a:visited:after { 358 | content: ""; 359 | } 360 | } 361 | 362 | /* Section anchors --------------------------------- 363 | Added in pandoc 2.11: https://github.com/jgm/pandoc-templates/commit/9904bf71 364 | */ 365 | 366 | div.csl-bib-body { } 367 | div.csl-entry { 368 | clear: both; 369 | } 370 | .hanging-indent div.csl-entry { 371 | margin-left:2em; 372 | text-indent:-2em; 373 | } 374 | div.csl-left-margin { 375 | min-width:2em; 376 | float:left; 377 | } 378 | div.csl-right-inline { 379 | margin-left:2em; 380 | padding-left:1em; 381 | } 382 | div.csl-indent { 383 | margin-left: 2em; 384 | } 385 | -------------------------------------------------------------------------------- /docs/pkgdown.js: -------------------------------------------------------------------------------- 1 | /* http://gregfranko.com/blog/jquery-best-practices/ */ 2 | (function($) { 3 | $(function() { 4 | 5 | $('nav.navbar').headroom(); 6 | 7 | Toc.init({ 8 | $nav: $("#toc"), 9 | $scope: $("main h2, main h3, main h4, main h5, main h6") 10 | }); 11 | 12 | if ($('#toc').length) { 13 | $('body').scrollspy({ 14 | target: '#toc', 15 | offset: $("nav.navbar").outerHeight() + 1 16 | }); 17 | } 18 | 19 | // Activate popovers 20 | $('[data-bs-toggle="popover"]').popover({ 21 | container: 'body', 22 | html: true, 23 | trigger: 'focus', 24 | placement: "top", 25 | sanitize: false, 26 | }); 27 | 28 | $('[data-bs-toggle="tooltip"]').tooltip(); 29 | 30 | /* Clipboard --------------------------*/ 31 | 32 | function changeTooltipMessage(element, msg) { 33 | var tooltipOriginalTitle=element.getAttribute('data-original-title'); 34 | element.setAttribute('data-original-title', msg); 35 | $(element).tooltip('show'); 36 | element.setAttribute('data-original-title', tooltipOriginalTitle); 37 | } 38 | 39 | if(ClipboardJS.isSupported()) { 40 | $(document).ready(function() { 41 | var copyButton = ""; 42 | 43 | $("div.sourceCode").addClass("hasCopyButton"); 44 | 45 | // Insert copy buttons: 46 | $(copyButton).prependTo(".hasCopyButton"); 47 | 48 | // Initialize tooltips: 49 | $('.btn-copy-ex').tooltip({container: 'body'}); 50 | 51 | // Initialize clipboard: 52 | var clipboard = new ClipboardJS('[data-clipboard-copy]', { 53 | text: function(trigger) { 54 | return trigger.parentNode.textContent.replace(/\n#>[^\n]*/g, ""); 55 | } 56 | }); 57 | 58 | clipboard.on('success', function(e) { 59 | changeTooltipMessage(e.trigger, 'Copied!'); 60 | e.clearSelection(); 61 | }); 62 | 63 | clipboard.on('error', function() { 64 | changeTooltipMessage(e.trigger,'Press Ctrl+C or Command+C to copy'); 65 | }); 66 | 67 | }); 68 | } 69 | 70 | /* Search marking --------------------------*/ 71 | var url = new URL(window.location.href); 72 | var toMark = url.searchParams.get("q"); 73 | var mark = new Mark("main#main"); 74 | if (toMark) { 75 | mark.mark(toMark, { 76 | accuracy: { 77 | value: "complementary", 78 | limiters: [",", ".", ":", "/"], 79 | } 80 | }); 81 | } 82 | 83 | /* Search --------------------------*/ 84 | /* Adapted from https://github.com/rstudio/bookdown/blob/2d692ba4b61f1e466c92e78fd712b0ab08c11d31/inst/resources/bs4_book/bs4_book.js#L25 */ 85 | // Initialise search index on focus 86 | var fuse; 87 | $("#search-input").focus(async function(e) { 88 | if (fuse) { 89 | return; 90 | } 91 | 92 | $(e.target).addClass("loading"); 93 | var response = await fetch($("#search-input").data("search-index")); 94 | var data = await response.json(); 95 | 96 | var options = { 97 | keys: ["what", "text", "code"], 98 | ignoreLocation: true, 99 | threshold: 0.1, 100 | includeMatches: true, 101 | includeScore: true, 102 | }; 103 | fuse = new Fuse(data, options); 104 | 105 | $(e.target).removeClass("loading"); 106 | }); 107 | 108 | // Use algolia autocomplete 109 | var options = { 110 | autoselect: true, 111 | debug: true, 112 | hint: false, 113 | minLength: 2, 114 | }; 115 | var q; 116 | async function searchFuse(query, callback) { 117 | await fuse; 118 | 119 | var items; 120 | if (!fuse) { 121 | items = []; 122 | } else { 123 | q = query; 124 | var results = fuse.search(query, { limit: 20 }); 125 | items = results 126 | .filter((x) => x.score <= 0.75) 127 | .map((x) => x.item); 128 | if (items.length === 0) { 129 | items = [{dir:"Sorry 😿",previous_headings:"",title:"No results found.",what:"No results found.",path:window.location.href}]; 130 | } 131 | } 132 | callback(items); 133 | } 134 | $("#search-input").autocomplete(options, [ 135 | { 136 | name: "content", 137 | source: searchFuse, 138 | templates: { 139 | suggestion: (s) => { 140 | if (s.title == s.what) { 141 | return `${s.dir} >
    ${s.title}
    `; 142 | } else if (s.previous_headings == "") { 143 | return `${s.dir} >
    ${s.title}
    > ${s.what}`; 144 | } else { 145 | return `${s.dir} >
    ${s.title}
    > ${s.previous_headings} > ${s.what}`; 146 | } 147 | }, 148 | }, 149 | }, 150 | ]).on('autocomplete:selected', function(event, s) { 151 | window.location.href = s.path + "?q=" + q + "#" + s.id; 152 | }); 153 | }); 154 | })(window.jQuery || window.$) 155 | 156 | 157 | -------------------------------------------------------------------------------- /docs/pkgdown.yml: -------------------------------------------------------------------------------- 1 | pandoc: 2.19.2 2 | pkgdown: 2.0.3 3 | pkgdown_sha: ~ 4 | articles: 5 | intro: intro.html 6 | last_built: 2022-12-20T01:41Z 7 | 8 | -------------------------------------------------------------------------------- /docs/reference/Rplot001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjake/headliner/efe92de137bf522160c89c603ee180088405bbc3/docs/reference/Rplot001.png -------------------------------------------------------------------------------- /docs/reference/Rplot002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjake/headliner/efe92de137bf522160c89c603ee180088405bbc3/docs/reference/Rplot002.png -------------------------------------------------------------------------------- /docs/reference/Rplot003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjake/headliner/efe92de137bf522160c89c603ee180088405bbc3/docs/reference/Rplot003.png -------------------------------------------------------------------------------- /docs/reference/animal_sleep.html: -------------------------------------------------------------------------------- 1 | 2 | Animal Sleep — animal_sleep • headliner 6 | 7 | 8 |
    9 |
    50 | 51 | 52 | 53 |
    54 |
    55 | 60 | 61 |
    62 |

    A modified subset of msleep. The data originally comes from V. M. Savage and G. B. West. A quantitative, theoretical framework for understanding mammalian sleep. Proceedings of the National Academy of Sciences, 104 (3):1051-1056, 2007.

    63 |
    64 | 65 |
    66 |
    animal_sleep
    67 |
    68 | 69 |
    70 |

    Format

    71 |

    A tibble with 56 rows and 7 columns:

    common_name
    72 |

    common name

    73 | 74 |
    order
    75 |

    Taxonomic order (between 'Class' and 'Family')

    76 | 77 |
    hours_asleep
    78 |

    total hours asleep in a 24-hour period

    79 | 80 |
    hours_awake
    81 |

    total hours awake in a 24-hour period

    82 | 83 |
    brain_kg
    84 |

    brain weight in kilograms

    85 | 86 |
    body_kg
    87 |

    body weight in kilograms

    88 | 89 | 90 |
    91 | 92 |
    93 | 96 |
    97 | 98 | 99 |
    102 | 103 |
    104 |

    Site built with pkgdown 2.0.3.

    105 |
    106 | 107 |
    108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /docs/reference/figures/facts_vs_insights.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjake/headliner/efe92de137bf522160c89c603ee180088405bbc3/docs/reference/figures/facts_vs_insights.png -------------------------------------------------------------------------------- /docs/reference/figures/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjake/headliner/efe92de137bf522160c89c603ee180088405bbc3/docs/reference/figures/logo.png -------------------------------------------------------------------------------- /docs/reference/figures/value_box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjake/headliner/efe92de137bf522160c89c603ee180088405bbc3/docs/reference/figures/value_box.png -------------------------------------------------------------------------------- /docs/reference/flights_jfk.html: -------------------------------------------------------------------------------- 1 | 2 | Flights from JFK Airport — flights_jfk • headliner 6 | 7 | 8 |
    9 |
    50 | 51 | 52 | 53 |
    54 |
    55 | 60 | 61 |
    62 |

    A modified subset of flights.

    63 |
    64 | 65 |
    66 |
    flights_jfk
    67 |
    68 | 69 |
    70 |

    Format

    71 |

    A tibble with 12,367 rows and 10 columns:

    year
    72 |

    year of arrival, 2012-2013

    73 | 74 |
    date
    75 |

    scheduled date of arrival

    76 | 77 |
    hour
    78 |

    hour of scheduled arrival, 9 AM to 9 PM

    79 | 80 |
    carrier
    81 |

    airline carrier, American Airlines (AA), Jet Blue (B6), 82 | and Delta (DL)

    83 | 84 |
    dest
    85 |

    destination, Las Vegas (LAS), Los Angeles (LAX), 86 | and San Francisco (SFO)

    87 | 88 |
    tailnum
    89 |

    tail number of plane

    90 | 91 |
    distance
    92 |

    distance from origin to destination

    93 | 94 |
    air_time
    95 |

    time in air from origin to destination

    96 | 97 |
    dep_delay
    98 |

    departure delay (minutes)

    99 | 100 |
    arr_delay
    101 |

    arrival delay (minutes)

    102 | 103 |
    temp
    104 |

    temperature (F)

    105 | 106 |
    dewp
    107 |

    dewpoint (F)

    108 | 109 |
    humid
    110 |

    relative humidity

    111 | 112 |
    wind_dir
    113 |

    wind direction (degrees)

    114 | 115 |
    wind_speed
    116 |

    wind speed (mph)

    117 | 118 |
    precip
    119 |

    precipitation (inches)

    120 | 121 |
    pressure
    122 |

    sea level pressure (millibars)

    123 | 124 |
    visib
    125 |

    visibility (miles)

    126 | 127 | 128 |
    129 | 130 |
    131 | 134 |
    135 | 136 | 137 |
    140 | 141 |
    142 |

    Site built with pkgdown 2.0.3.

    143 |
    144 | 145 |
    146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | -------------------------------------------------------------------------------- /docs/reference/pipe.html: -------------------------------------------------------------------------------- 1 | 2 | Pipe operator — %>% • headliner 6 | 7 | 8 |
    9 |
    50 | 51 | 52 | 53 |
    54 |
    55 | 60 | 61 |
    62 |

    See magrittr::%>% for details.

    63 |
    64 | 65 |
    66 |
    lhs %>% rhs
    67 |
    68 | 69 | 70 |
    71 | 74 |
    75 | 76 | 77 |
    80 | 81 |
    82 |

    Site built with pkgdown 2.0.3.

    83 |
    84 | 85 |
    86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /docs/reference/pixar_films-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjake/headliner/efe92de137bf522160c89c603ee180088405bbc3/docs/reference/pixar_films-1.png -------------------------------------------------------------------------------- /docs/reference/pixar_films-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjake/headliner/efe92de137bf522160c89c603ee180088405bbc3/docs/reference/pixar_films-2.png -------------------------------------------------------------------------------- /docs/reference/pixar_films-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjake/headliner/efe92de137bf522160c89c603ee180088405bbc3/docs/reference/pixar_films-3.png -------------------------------------------------------------------------------- /docs/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /404.html 5 | 6 | 7 | /articles/index.html 8 | 9 | 10 | /articles/intro.html 11 | 12 | 13 | /authors.html 14 | 15 | 16 | /CODE_OF_CONDUCT.html 17 | 18 | 19 | /CONTRIBUTING.html 20 | 21 | 22 | /index.html 23 | 24 | 25 | /LICENSE-text.html 26 | 27 | 28 | /LICENSE.html 29 | 30 | 31 | /news/index.html 32 | 33 | 34 | /reference/add_article.html 35 | 36 | 37 | /reference/add_date_columns.html 38 | 39 | 40 | /reference/add_headline_column.html 41 | 42 | 43 | /reference/animal_sleep.html 44 | 45 | 46 | /reference/compare_conditions.html 47 | 48 | 49 | /reference/compare_values.html 50 | 51 | 52 | /reference/demo_data.html 53 | 54 | 55 | /reference/flights_jfk.html 56 | 57 | 58 | /reference/headline.html 59 | 60 | 61 | /reference/index.html 62 | 63 | 64 | /reference/pipe.html 65 | 66 | 67 | /reference/pixar_films.html 68 | 69 | 70 | /reference/plural_phrasing.html 71 | 72 | 73 | /reference/trend_terms.html 74 | 75 | 76 | /reference/view_list.html 77 | 78 | 79 | /SUPPORT.html 80 | 81 | 82 | -------------------------------------------------------------------------------- /headliner.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: No 4 | SaveWorkspace: No 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | LineEndingConversion: Posix 18 | 19 | BuildType: Package 20 | PackageUseDevtools: Yes 21 | PackageInstallArgs: --no-multiarch --with-keep.source 22 | PackageRoxygenize: rd,collate,namespace 23 | -------------------------------------------------------------------------------- /inst/WORDLIST: -------------------------------------------------------------------------------- 1 | aeiou 2 | CMD 3 | codecov 4 | dev 5 | dewpoint 6 | DL 7 | dplyr 8 | github 9 | Leung 10 | lst 11 | Metacritic 12 | MPA 13 | na 14 | Pixar 15 | purrr 16 | sd 17 | SFO 18 | tibble 19 | -------------------------------------------------------------------------------- /man/add_article.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/string-manipulation.R 3 | \name{add_article} 4 | \alias{add_article} 5 | \title{Append a/an to word} 6 | \usage{ 7 | add_article(x) 8 | } 9 | \arguments{ 10 | \item{x}{string or numeric value} 11 | } 12 | \value{ 13 | Returns a vector the same length as the input. 14 | } 15 | \description{ 16 | Append a/an to word 17 | } 18 | \details{ 19 | This function uses crude logic to append 'a' or 'an' to numbers and phrases. 20 | \itemize{ 21 | \item words that start with aeiou 22 | \item negative numbers always start with 'a', ex: 'a -3' or 'a -8' 23 | \item decimals always start with 'a' ex: 0.4 is usually pronounced 24 | 'a zero point four' or 'a point four' 25 | \item numbers starting with 8 are always 'an' 26 | \item if the integer that comes after thousand or million is 11 or 18 then 'an' 27 | \itemize{ 28 | \item 18,000 becomes 18 and that becomes 'an 18' 29 | } 30 | \item if the integer that comes after thousand or million is in 31 | 1, 10, 12, 13, 14, 15, 16, 17, 19 then 'a' 32 | \itemize{ 33 | \item 15,500 becomes 15 and that becomes 'a 15' 34 | } 35 | \item otherwise 'a' 36 | } 37 | } 38 | \examples{ 39 | add_article("increase") 40 | 41 | add_article("decrease") 42 | 43 | add_article(c(1, 8, 10, 11, 18, 20, 80)) 44 | 45 | add_article(18123) 46 | 47 | stats::setNames( 48 | add_article(1.8 * 10^(1:7)), 49 | prettyNum(1.8 * 10^(1:7), big.mark = ",") 50 | ) 51 | 52 | } 53 | -------------------------------------------------------------------------------- /man/add_date_columns.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/add-date-columns.R 3 | \name{add_date_columns} 4 | \alias{add_date_columns} 5 | \title{Add columns with date calculations based on reference date} 6 | \usage{ 7 | add_date_columns( 8 | df, 9 | date_col, 10 | ref_date = Sys.Date(), 11 | fiscal_year_offset = 6, 12 | week_start = 1, 13 | drop = FALSE 14 | ) 15 | } 16 | \arguments{ 17 | \item{df}{data frame} 18 | 19 | \item{date_col}{column with class of 'date'} 20 | 21 | \item{ref_date}{reference date for calculations, defaults to current date} 22 | 23 | \item{fiscal_year_offset}{the number of months to offset date, if fiscal 24 | year ends in June, use 6} 25 | 26 | \item{week_start}{integer for start of week where Monday = 1 and Sunday = 7} 27 | 28 | \item{drop}{some of the generated fields may match the input data frame. When 29 | TRUE, the original columns will be removed and replaced with the new field 30 | of the same name. Otherwise, columns with the same name will be appended with 31 | a '1'} 32 | } 33 | \value{ 34 | Returns a data frame with columns appended to describe 35 | date distances from a reference date. 36 | } 37 | \description{ 38 | Using a reference date (defaults to current date), columns are 39 | appended to the data set describing the number of days, weeks, months, 40 | quarters, calendar years and fiscal years since the reference date. If 41 | the new columns share names with an existing column, the function will show 42 | a warning. 43 | } 44 | \examples{ 45 | demo_data() |> 46 | add_date_columns(date_col = date) 47 | 48 | # if columns overlap, you will see a warning 49 | demo_data() |> 50 | dplyr::mutate(week = 1) |> 51 | add_date_columns(date_col = date) 52 | 53 | # to drop the old column and keep the new column use `drop = TRUE` 54 | demo_data() |> 55 | dplyr::mutate(week = 1) |> 56 | add_date_columns(date_col = date, drop = TRUE) 57 | } 58 | -------------------------------------------------------------------------------- /man/add_headline_column.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/add-headline-column.R 3 | \name{add_headline_column} 4 | \alias{add_headline_column} 5 | \title{Add column of headlines} 6 | \usage{ 7 | add_headline_column( 8 | df, 9 | x, 10 | y, 11 | headline = "{trend} of {delta} ({orig_values})", 12 | ..., 13 | .name = "headline", 14 | if_match = "There was no difference", 15 | trend_phrases = headliner::trend_terms(), 16 | plural_phrases = NULL, 17 | orig_values = "{x} vs. {y}", 18 | n_decimal = 1, 19 | round_all = TRUE, 20 | multiplier = 1, 21 | return_cols = .name 22 | ) 23 | } 24 | \arguments{ 25 | \item{df}{data frame, must be a single row} 26 | 27 | \item{x}{a numeric value to compare to the reference value of 'y'} 28 | 29 | \item{y}{a numeric value to act as a control for the 'x' value} 30 | 31 | \item{headline}{a string to format the final output. Uses 32 | \code{\link[glue]{glue}} syntax} 33 | 34 | \item{...}{arguments passed to \code{\link[glue]{glue_data}}} 35 | 36 | \item{.name}{string value for the name of the new column to create} 37 | 38 | \item{if_match}{string to display if numbers match, uses 39 | \code{\link[glue]{glue}} syntax} 40 | 41 | \item{trend_phrases}{list of values to use for when x is more than y 42 | or x is less than y. You can pass it just 43 | \code{\link{trend_terms}} (the default) and call the result with 44 | \code{"...{trend}..."} or pass is a named list (see examples)} 45 | 46 | \item{plural_phrases}{named list of values to use when difference (delta) is 47 | singular (delta = 1) or plural (delta != 1)} 48 | 49 | \item{orig_values}{a string using \code{\link[glue]{glue}} syntax. 50 | example: \verb{(\{x\} vs \{y\})}} 51 | 52 | \item{n_decimal}{numeric value to limit the number of decimal places in 53 | the returned values.} 54 | 55 | \item{round_all}{logical value to indicate if all values should be rounded. 56 | When FALSE, the values will return with no modification. When TRUE (default) 57 | all values will be round to the length specified by 'n_decimal'.} 58 | 59 | \item{multiplier}{number indicating the scaling factor. When multiplier = 1 60 | (default), 0.25 will return 0.25. When multiplier = 100, 0.25 will return 25.} 61 | 62 | \item{return_cols}{arguments that can be passed to 63 | \code{\link[dplyr]{select}}, ex: c("a", "b"), 64 | \code{\link[dplyr]{starts_with}}, etc.} 65 | } 66 | \value{ 67 | Returns the original data frame with columns appended. 68 | } 69 | \description{ 70 | This works similar to \code{headline()} but acts on and returns a 71 | data frame. 72 | } 73 | \details{ 74 | What is nice about this function is you can return some of the 75 | "talking points" used in the headline calculation. For example, if you want 76 | to find the most extreme headlines, you can use 77 | \code{add_headline_column(..., return_cols = delta)} This will bring back a 78 | \code{headline} column as well as the \code{delta} talking point (the absolute 79 | difference between \code{x} and \code{y}). With this result, you can sort in descending 80 | order and filter for the biggest difference. 81 | } 82 | \examples{ 83 | 84 | # You can use 'add_headline_column()' to reference values in an existing data set. 85 | # Here is an example comparing the box office sales of different Pixar films 86 | head(pixar_films) |> 87 | dplyr::select(film, bo_domestic, bo_intl) |> 88 | add_headline_column( 89 | x = bo_domestic, 90 | y = bo_intl, 91 | headline = "{film} was ${delta}M higher {trend} (${x}M vs ${y}M)", 92 | trend_phrases = trend_terms(more = "domestically", less = "internationally") 93 | ) |> 94 | knitr::kable("pandoc") 95 | 96 | # You can also use 'return_cols' to return any and all "talking points". 97 | # You can use tidyselect helpers like 'starts_with("delta")' or 98 | # 'everything()'. In this example, I returned the 'raw_delta' & 'trend' columns 99 | # and then identified the records at the extremes 100 | pixar_films |> 101 | dplyr::select(film, bo_domestic, bo_intl) |> 102 | add_headline_column( 103 | x = bo_domestic, 104 | y = bo_intl, 105 | headline = "${delta}M {trend} (${x}M vs ${y}M)", 106 | trend_phrases = trend_terms(more = "higher", less = "lower"), 107 | return_cols = c(raw_delta, trend) 108 | ) |> 109 | dplyr::filter(raw_delta \%in\% range(raw_delta)) |> 110 | knitr::kable("pandoc") 111 | 112 | } 113 | -------------------------------------------------------------------------------- /man/compare_conditions.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/compare-conditions.R 3 | \name{compare_conditions} 4 | \alias{compare_conditions} 5 | \title{Compare two conditions within a data frame} 6 | \usage{ 7 | compare_conditions(df, x, y, .cols = everything(), .fns = lst(mean)) 8 | } 9 | \arguments{ 10 | \item{df}{data frame} 11 | 12 | \item{x}{condition for comparison, same criteria you would use in 13 | 'dplyr::filter', used in contrast to the reference group 'y'} 14 | 15 | \item{y}{condition for comparison, same criteria you would use in 16 | 'dplyr::filter', used in contrast to the reference group 'x'} 17 | 18 | \item{.cols}{columns to use in comparison} 19 | 20 | \item{.fns}{named list of the functions to use, ex: 21 | list(avg = mean, sd = sd) 'purrr' style phrases are also supported like 22 | list(mean = ~mean(.x, na.rm = TRUE), sd = sd) and dplyr::lst(mean, sd) will 23 | create a list(mean = mean, sd = sd)} 24 | } 25 | \value{ 26 | Returns a data frame that is either 1 row, or if grouped, 27 | 1 row per group. 28 | } 29 | \description{ 30 | Using logic that \code{\link[dplyr]{filter}} can interpret, 31 | \code{compare_conditions()} will summarize the data aggregating condition \code{x} and 32 | condition \code{y} 33 | } 34 | \details{ 35 | \code{compare_conditions()} passes its arguments to 36 | \code{\link[dplyr]{across}}. The \code{.cols} and \code{.fns} work the same. For 37 | clarity, it is helpful to use the \code{\link[dplyr]{lst}} function for the 38 | \code{.fns} parameter. Using 39 | \code{compare_conditions(..., .cols = my_var, .fns = lst(mean, sd))} will return 40 | the values \code{mean_my_var_x}, \code{mean_my_var_y}, \code{sd_my_var_x} and \code{sd_my_var_x} 41 | } 42 | \examples{ 43 | 44 | # compare_conditions works similar to dplyr::across() 45 | pixar_films |> 46 | compare_conditions( 47 | x = (rating == "G"), 48 | y = (rating == "PG"), 49 | .cols = rotten_tomatoes 50 | ) 51 | 52 | 53 | # because data frames are just fancy lists, you pass the result to headline_list() 54 | pixar_films |> 55 | compare_conditions( 56 | x = (rating == "G"), 57 | y = (rating == "PG"), 58 | .cols = rotten_tomatoes 59 | ) |> 60 | headline_list("a difference of {delta} points") 61 | 62 | 63 | # you can return multiple objects to compare 64 | # 'view_List()' is a helper to see list objects in a compact way 65 | pixar_films |> 66 | compare_conditions( 67 | x = (rating == "G"), 68 | y = (rating == "PG"), 69 | .cols = c(rotten_tomatoes, metacritic), 70 | .fns = dplyr::lst(mean, sd) 71 | ) |> 72 | view_list() 73 | 74 | 75 | # you can use any of the `tidyselect` helpers 76 | pixar_films |> 77 | compare_conditions( 78 | x = (rating == "G"), 79 | y = (rating == "PG"), 80 | .cols = dplyr::starts_with("bo_") 81 | ) 82 | 83 | 84 | # if you want to compare x to the overall average, use y = TRUE 85 | pixar_films |> 86 | compare_conditions( 87 | x = (rating == "G"), 88 | y = TRUE, 89 | .cols = rotten_tomatoes 90 | ) 91 | 92 | 93 | # to get the # of observations use length() instead of n() 94 | # note: don't pass the parentheses 95 | pixar_films |> 96 | compare_conditions( 97 | x = (rating == "G"), 98 | y = (rating == "PG"), 99 | .cols = rotten_tomatoes, # can put anything here really 100 | .fns = list(n = length) 101 | ) 102 | 103 | 104 | # you can also use purrr-style lambdas 105 | pixar_films |> 106 | compare_conditions( 107 | x = (rating == "G"), 108 | y = (rating == "PG"), 109 | .cols = rotten_tomatoes, 110 | .fns = list(avg = ~ sum(.x) / length(.x)) 111 | ) 112 | 113 | # you can compare categorical data with functions like dplyr::n_distinct() 114 | pixar_films |> 115 | compare_conditions( 116 | x = (rating == "G"), 117 | y = (rating == "PG"), 118 | .cols = film, 119 | .fns = list(distinct = dplyr::n_distinct) 120 | ) 121 | } 122 | -------------------------------------------------------------------------------- /man/compare_values.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/compare-values.R 3 | \name{compare_values} 4 | \alias{compare_values} 5 | \title{Compare two values and get talking points} 6 | \usage{ 7 | compare_values( 8 | x, 9 | y, 10 | trend_phrases = headliner::trend_terms(), 11 | orig_values = "{x} vs. {y}", 12 | plural_phrases = NULL, 13 | n_decimal = 1, 14 | round_all = TRUE, 15 | multiplier = 1, 16 | check_rounding = TRUE 17 | ) 18 | } 19 | \arguments{ 20 | \item{x}{a numeric value to compare to the reference value of 'y'} 21 | 22 | \item{y}{a numeric value to act as a control for the 'x' value} 23 | 24 | \item{trend_phrases}{list of values to use for when x is more than y 25 | or x is less than y. You can pass it just 26 | \code{\link{trend_terms}} (the default) and call the result with 27 | \code{"...{trend}..."} or pass is a named list (see examples)} 28 | 29 | \item{orig_values}{a string using \code{\link[glue]{glue}} syntax. 30 | example: \verb{(\{x\} vs \{y\})}} 31 | 32 | \item{plural_phrases}{named list of values to use when difference (delta) is 33 | singular (delta = 1) or plural (delta != 1)} 34 | 35 | \item{n_decimal}{numeric value to limit the number of decimal places in 36 | the returned values.} 37 | 38 | \item{round_all}{logical value to indicate if all values should be rounded. 39 | When FALSE, the values will return with no modification. When TRUE (default) 40 | all values will be round to the length specified by 'n_decimal'.} 41 | 42 | \item{multiplier}{number indicating the scaling factor. When multiplier = 1 43 | (default), 0.25 will return 0.25. When multiplier = 100, 0.25 will return 25.} 44 | 45 | \item{check_rounding}{when TRUE (default) inputs will be checked to confirm if 46 | a difference of zero may be due to rounding. Ex: 0.16 and 0.24 with 47 | 'n_decimal = 1' will both return 0.2. Because this will show no difference, 48 | a message will be displayed} 49 | } 50 | \value{ 51 | \code{compare_values()} returns a list object that can be used with 52 | \code{\link[glue]{glue}} syntax 53 | } 54 | \description{ 55 | A function to create "talking points" that 56 | describes the difference between two values. 57 | } 58 | \details{ 59 | Given \code{compare_values(x = 8, y = 10)} the following items will be returned 60 | in the list:\tabular{lll}{ 61 | item \tab value \tab description \cr 62 | \code{x} \tab 2 \tab original \code{x} value to compare against \code{y} \cr 63 | \code{y} \tab 10 \tab original \code{y} value \cr 64 | \code{delta} \tab 8 \tab absolute difference between \code{x} & \code{y} \cr 65 | \code{delta_p} \tab 80 \tab \% difference between \code{x} & \code{y} \cr 66 | \code{article_delta} \tab "an 8" \tab \code{delta} with the article included \cr 67 | \code{article_delta_p} \tab "an 80" \tab \code{delta_p} with the article included \cr 68 | \code{raw_delta} \tab -8 \tab true difference between \code{x} & \code{y} \cr 69 | \code{raw_delta_p} \tab -80 \tab true \% difference between \code{x} & \code{y} \cr 70 | \code{article_raw_delta} \tab "a -8" \tab \code{raw_delta} with the article \cr 71 | \code{article_raw_delta_p} \tab "a -80" \tab \code{raw_delta_p} with the article \cr 72 | \code{sign} \tab -1 \tab the direction, 1 (increase), -1 (decrease), or 0 (no change) \cr 73 | \code{orig_values} \tab "2 vs. 10" \tab shorthand for \verb{\{x\} vs \{y\}} \cr 74 | \code{trend} \tab "decrease" \tab influenced by the values in \code{trend_phrases} argument \cr 75 | } 76 | } 77 | \examples{ 78 | # the values can be manually entered 79 | 80 | compare_values(10, 8) |> head(2) 81 | # percent difference (10-8)/8 82 | compare_values(10, 8)$delta_p 83 | 84 | # trend_phrases returns an object called trend if nothing is passed 85 | compare_values(10, 8)$trend 86 | 87 | # or if one argument is passed using trend_terms() 88 | compare_values(10, 8, trend_phrases = trend_terms(more = "higher"))$trend 89 | 90 | # if a named list is used, the objects are called by their names 91 | compare_values( 92 | 10, 8, 93 | trend_phrases = list( 94 | more = trend_terms(), 95 | higher = trend_terms("higher", "lower") 96 | ) 97 | )$higher 98 | 99 | # a phrase about the comparison can be edited by providing glue syntax 100 | # 'c' = the 'compare' value, 'r' = 'reference' 101 | compare_values(10, 8, orig_values = "{x} to {y} people")$orig_values 102 | 103 | # you can also adjust the rounding, although the default is 1 104 | compare_values(0.1234, 0.4321)$orig_values 105 | compare_values(0.1234, 0.4321, n_decimal = 3)$orig_values 106 | # or add a multiplier 107 | compare_values(0.1234, 0.4321, multiplier = 100)$orig_values 108 | } 109 | \seealso{ 110 | \code{\link[=headline]{headline()}}, \code{\link[=trend_terms]{trend_terms()}}, \code{\link[=plural_phrasing]{plural_phrasing()}} and \code{\link[=view_list]{view_list()}} 111 | } 112 | -------------------------------------------------------------------------------- /man/demo_data.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/demo-data.R 3 | \name{demo_data} 4 | \alias{demo_data} 5 | \title{Small data set referencing the current date} 6 | \usage{ 7 | demo_data(n = 10, by = "-2 month") 8 | } 9 | \arguments{ 10 | \item{n}{number of rows to return} 11 | 12 | \item{by}{string indicating the unit of time between dates in 13 | \code{seq.Date(..., by = )}} 14 | } 15 | \value{ 16 | Returns a data frame of size \code{n}. 17 | } 18 | \description{ 19 | Small data set referencing the current date 20 | } 21 | \examples{ 22 | demo_data() 23 | 24 | demo_data(n = 8, by = "1 day") 25 | } 26 | -------------------------------------------------------------------------------- /man/figures/facts_vs_insights.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjake/headliner/efe92de137bf522160c89c603ee180088405bbc3/man/figures/facts_vs_insights.png -------------------------------------------------------------------------------- /man/figures/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjake/headliner/efe92de137bf522160c89c603ee180088405bbc3/man/figures/logo.png -------------------------------------------------------------------------------- /man/figures/value_box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjake/headliner/efe92de137bf522160c89c603ee180088405bbc3/man/figures/value_box.png -------------------------------------------------------------------------------- /man/headline.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/headline.R 3 | \name{headline} 4 | \alias{headline} 5 | \alias{headline_list} 6 | \title{Compose phrases that describe differences in the data} 7 | \usage{ 8 | headline( 9 | x, 10 | y, 11 | headline = "{trend} of {delta} ({orig_values})", 12 | ..., 13 | if_match = "There was no difference", 14 | trend_phrases = headliner::trend_terms(), 15 | plural_phrases = NULL, 16 | orig_values = "{x} vs. {y}", 17 | n_decimal = 1, 18 | round_all = TRUE, 19 | multiplier = 1, 20 | return_data = FALSE 21 | ) 22 | 23 | headline_list( 24 | l, 25 | headline = "{trend} of {delta} ({orig_values})", 26 | x, 27 | y, 28 | ..., 29 | if_match = "There was no difference.", 30 | trend_phrases = headliner::trend_terms(), 31 | plural_phrases = NULL, 32 | orig_values = "{x} vs. {y}", 33 | n_decimal = 1, 34 | round_all = TRUE, 35 | multiplier = 1, 36 | return_data = FALSE 37 | ) 38 | } 39 | \arguments{ 40 | \item{x}{a numeric value to compare to the reference value of 'y'} 41 | 42 | \item{y}{a numeric value to act as a control for the 'x' value} 43 | 44 | \item{headline}{a string to format the final output. Uses 45 | \code{\link[glue]{glue}} syntax} 46 | 47 | \item{...}{arguments passed to \code{\link[glue]{glue_data}}} 48 | 49 | \item{if_match}{string to display if numbers match, uses 50 | \code{\link[glue]{glue}} syntax} 51 | 52 | \item{trend_phrases}{list of values to use for when x is more than y 53 | or x is less than y. You can pass it just 54 | \code{\link{trend_terms}} (the default) and call the result with 55 | \code{"...{trend}..."} or pass is a named list (see examples)} 56 | 57 | \item{plural_phrases}{named list of values to use when difference (delta) is 58 | singular (delta = 1) or plural (delta != 1)} 59 | 60 | \item{orig_values}{a string using \code{\link[glue]{glue}} syntax. 61 | example: \verb{(\{x\} vs \{y\})}} 62 | 63 | \item{n_decimal}{numeric value to limit the number of decimal places in 64 | the returned values.} 65 | 66 | \item{round_all}{logical value to indicate if all values should be rounded. 67 | When FALSE, the values will return with no modification. When TRUE (default) 68 | all values will be round to the length specified by 'n_decimal'.} 69 | 70 | \item{multiplier}{number indicating the scaling factor. When multiplier = 1 71 | (default), 0.25 will return 0.25. When multiplier = 100, 0.25 will return 25.} 72 | 73 | \item{return_data}{logical to indicate whether function should return the 74 | talking points used to compose the headline} 75 | 76 | \item{l}{a list with values to compare, if named, can call by name} 77 | } 78 | \value{ 79 | Returns a character vector the same length as the input, 80 | } 81 | \description{ 82 | Given two values, \code{headline()} will use 83 | \code{\link[glue]{glue}} syntax to string together "talking points". 84 | For example \code{headline(8, 10)} will describe a difference of 2 and can be 85 | expressed as 86 | \code{headline(8, 10, headline = "changed by {delta} ({raw_delta_p}\%)")}. This 87 | returns "changed by 2 (-20\%)". 88 | } 89 | \details{ 90 | \code{headline()} relies heavily on \code{\link[glue]{glue_data}}. 91 | Objects can be combined into a headline using the following search path: 92 | If given 93 | 94 | \if{html}{\out{
    }}\preformatted{delta <- 123 95 | headline(1, 3, delta = "abc") 96 | }\if{html}{\out{
    }} 97 | 98 | \if{html}{\out{
    }}\preformatted{## decrease of abc (1 vs. 3) 99 | }\if{html}{\out{
    }} 100 | 101 | \code{delta} is one of the "talking points" from \code{compare_values()} and would 102 | usually return "2" but because we passed the named variable 103 | \code{delta = "none"}, \code{headline()} (really \code{\link[glue]{glue_data}}) 104 | will look first at the named variables, then at the result of 105 | \code{compare_values()} then in the global environment. So in the example above, 106 | the output will return \code{"decrease of xxxxxx (1 vs. 3)"} 107 | } 108 | \examples{ 109 | # values can be manually entered, some headlines are provided by default 110 | headline(10, 8) 111 | headline(8, 10) 112 | headline(1:3, 3:1) 113 | 114 | # most likely you'll edit the headline by hand 115 | headline( 116 | x = 10, 117 | y = 8, 118 | headline = "There was a ${delta} {trend} vs last year" 119 | ) 120 | 121 | # you can also adjust the phrasing of higher/lower values 122 | headline( 123 | x = 10, 124 | y = 8, 125 | headline = "Group A was {trend} by {delta_p}\%.", 126 | trend_phrases = trend_terms(more = "higher", less = "lower") 127 | ) 128 | 129 | # a phrase about the comparion can be edited by providing glue syntax 130 | # 'c' = the 'compare' value, 'r' = 'reference' 131 | headline(10, 8, orig_values = "{x} to {y} people") 132 | 133 | # you can also add phrases for when the difference = 1 or not 134 | headline( 135 | x = 10, 136 | y = 8, 137 | plural_phrases = list( 138 | were = plural_phrasing(single = "was", multi = "were"), 139 | people = plural_phrasing(single = "person", multi = "people") 140 | ), 141 | headline = "there {were} {delta} {people}" 142 | ) 143 | 144 | # you can also adjust the rounding, the default is 1 145 | headline(0.1234, 0.4321) 146 | headline(0.1234, 0.4321, n_decimal = 3) 147 | # or use a multiplier 148 | headline(0.1234, 0.4321, multiplier = 100) 149 | 150 | # there are many components you can assemble 151 | headline( 152 | x = 16, 153 | y = 8, 154 | headline = "there was {article_delta_p}\% {trend}, \\\\ 155 | {add_article(trend)} of {delta} ({orig_values})" 156 | ) 157 | 158 | 159 | # compare_conditions() produces a one-row data frame that can be 160 | # passed to headline_list() 161 | pixar_films |> 162 | compare_conditions( 163 | x = (rating == "G"), 164 | y = (rating == "PG"), 165 | rotten_tomatoes 166 | ) |> 167 | headline_list( 168 | headline = "On average, G-rated films score {delta} points {trend} than \\\\ 169 | PG films on Rotten Tomatoes", 170 | trend_phrases = trend_terms(more = "higher", less = "lower") 171 | ) 172 | 173 | # if you have more than 2 list items, you can specify them by name 174 | list( 175 | x = 1, 176 | y = 2, 177 | z = 3 178 | ) |> 179 | headline_list( 180 | x = x, 181 | y = z 182 | ) 183 | } 184 | \seealso{ 185 | \code{\link[=compare_values]{compare_values()}}, \code{\link[=trend_terms]{trend_terms()}}, and \code{\link[=add_article]{add_article()}} 186 | } 187 | -------------------------------------------------------------------------------- /man/pixar_films.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/data.R 3 | \docType{data} 4 | \name{pixar_films} 5 | \alias{pixar_films} 6 | \title{This data comes from \href{https://github.com/erictleung/pixarfilms/}{\code{pixarfilms}} 7 | package by Eric Leung (2022)} 8 | \format{ 9 | A tibble with 22 rows and 10 columns: 10 | \describe{ 11 | \item{order}{order of release} 12 | \item{film}{name of film} 13 | \item{release_date}{date film premiered} 14 | \item{year}{the year the film premiered} 15 | \item{run_time}{film length in minutes} 16 | \item{film_rating}{rating based on Motion Picture Association (MPA) film 17 | rating system} 18 | \item{rotten_tomatoes}{score from the American review-aggregation website 19 | Rotten Tomatoes; scored out of 100} 20 | \item{metacritic}{score from Metacritic where scores are weighted average 21 | of reviews; scored out of 100} 22 | \item{bo_domestic}{box office gross amount in U.S. dollars (millions) for 23 | U.S. and Canada} 24 | \item{bo_intl}{box office gross amount in U.S. dollars (millions) for other 25 | territories} 26 | } 27 | } 28 | \usage{ 29 | pixar_films 30 | } 31 | \description{ 32 | The data has box office sales, audience ratings, and release dates for each Pixar film 33 | } 34 | \examples{ 35 | pixar_films 36 | 37 | library(ggplot2) 38 | 39 | headline( 40 | x = min(pixar_films$run_time), 41 | y = max(pixar_films$run_time), 42 | headline = 43 | "The shortest film was {delta} minutes less than the longest film ({orig_values} minutes)" 44 | ) 45 | 46 | ggplot(pixar_films, aes(bo_intl, rating)) + 47 | geom_boxplot() + 48 | xlim(0, NA) + 49 | labs(title = "International Box Office by MPA Rating") 50 | 51 | 52 | ggplot(pixar_films, aes(release_date, run_time)) + 53 | geom_line() + 54 | geom_point() + 55 | ylim(0, NA) + 56 | labs(title = "Film runtimes by release date") 57 | 58 | 59 | ggplot(pixar_films, aes(y = reorder(film, rotten_tomatoes))) + 60 | geom_linerange(aes(xmin = rotten_tomatoes, xmax = metacritic), size = 2, color = "grey85") + 61 | geom_point(aes(x = rotten_tomatoes, color = "rotten_tomatoes")) + 62 | geom_point(aes(x = metacritic, color = "metacritic")) + 63 | scale_color_manual(values = c("steelblue1", "coral2")) + 64 | theme_minimal(base_size = 9) + 65 | labs( 66 | title = "Rotten Tomatoes vs Metacritic by film", 67 | color = NULL, 68 | y = NULL, 69 | x = "Audience Score" 70 | ) 71 | 72 | } 73 | \keyword{datasets} 74 | -------------------------------------------------------------------------------- /man/plural_phrasing.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/plural-phrasing.R 3 | \name{plural_phrasing} 4 | \alias{plural_phrasing} 5 | \title{List of values to use when change is plural (or singular)} 6 | \usage{ 7 | plural_phrasing(single, multi) 8 | } 9 | \arguments{ 10 | \item{single}{string to use when delta = 1} 11 | 12 | \item{multi}{string to use when delta > 1} 13 | } 14 | \value{ 15 | Returns a list object. 16 | } 17 | \description{ 18 | \code{plural_phrasing()} returns a list object describing the value 19 | to use when display when \code{x - y} is 1 (single) or not one (multiple or 20 | fraction). This helps write "1 person" vs "2 people" 21 | } 22 | \details{ 23 | \code{plural_phrasing()} will primarily be used in \code{headline()} and 24 | passed along to \code{compare_conditions()}. Similar to \code{trend_terms()}. 25 | Plural phrases can be passed in a list. See examples below. 26 | } 27 | \examples{ 28 | plural_phrasing(single = "person", multi = "people") 29 | 30 | headline( 31 | x = 1:2, 32 | y = 0, 33 | headline = "a difference of {delta} {people}", 34 | plural_phrases = list(people = plural_phrasing("person", "people")) 35 | ) 36 | 37 | 38 | # a complex example passing multiple trends and plural phrases 39 | headline( 40 | 35, 30, 41 | headline = 42 | "We had {an_increase} of {delta} {people}. 43 | That is {delta} {more} {employees} \\\\ 44 | than the same time last year ({orig_values}).", 45 | trend_phrases = list( 46 | an_increase = trend_terms("an increase", "a decrease"), 47 | more = trend_terms("more", "less") 48 | ), 49 | plural_phrases = 50 | list( 51 | people = plural_phrasing("person", "people"), 52 | employees = plural_phrasing("employee", "employees") 53 | ) 54 | ) 55 | } 56 | -------------------------------------------------------------------------------- /man/trend_terms.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/trend-terms.R 3 | \name{trend_terms} 4 | \alias{trend_terms} 5 | \title{Phrases for direction of difference} 6 | \usage{ 7 | trend_terms(more = "increase", less = "decrease") 8 | } 9 | \arguments{ 10 | \item{more}{string to use when x > y} 11 | 12 | \item{less}{string to use when x < y} 13 | } 14 | \value{ 15 | Returns a list object. 16 | } 17 | \description{ 18 | \code{trend_terms()} returns a list object describing the values to 19 | display when \code{x} is greater than \code{y} or \code{x} is less than \code{y}. 20 | } 21 | \details{ 22 | \code{trend_terms()} will primarily be used in \code{headline()} and passed 23 | along to \code{compare_conditions()}. Similar to \code{plural_phrasing()} Trend terms 24 | can be passed in a list. See examples below. 25 | } 26 | \examples{ 27 | 28 | headline( 29 | x = c(9, 11), 30 | y = 10, 31 | headline = "{trend} by {delta_p}\%", 32 | trend_phrases = trend_terms("higher", "lower") 33 | ) 34 | 35 | # a complex example passing multiple trends and plural phrases 36 | headline( 37 | 35, 30, 38 | headline = 39 | "We had {an_increase} of {delta} {people}. 40 | That is {delta} {more} {employees} \\\\ 41 | than the same time last year ({orig_values}).", 42 | trend_phrases = list( 43 | an_increase = trend_terms("an increase", "a decrease"), 44 | more = trend_terms("more", "less") 45 | ), 46 | plural_phrases = 47 | list( 48 | people = plural_phrasing("person", "people"), 49 | employees = plural_phrasing("employee", "employees") 50 | ) 51 | ) 52 | } 53 | \seealso{ 54 | \code{\link[=compare_values]{compare_values()}} 55 | } 56 | -------------------------------------------------------------------------------- /man/view_list.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/view-list.R 3 | \name{view_list} 4 | \alias{view_list} 5 | \title{Compact view of list values} 6 | \usage{ 7 | view_list(x) 8 | } 9 | \arguments{ 10 | \item{x}{a vector or list to be transposed} 11 | } 12 | \value{ 13 | Returns a data frame to display a list or vector vertically. 14 | } 15 | \description{ 16 | Compact view of list values 17 | } 18 | \examples{ 19 | compare_values(10, 8) |> 20 | view_list() 21 | 22 | add_article(c(1,8,10, 11, 18)) |> 23 | view_list() 24 | } 25 | \seealso{ 26 | \code{\link[=compare_values]{compare_values()}} 27 | } 28 | -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjake/headliner/efe92de137bf522160c89c603ee180088405bbc3/pkgdown/favicon/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjake/headliner/efe92de137bf522160c89c603ee180088405bbc3/pkgdown/favicon/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjake/headliner/efe92de137bf522160c89c603ee180088405bbc3/pkgdown/favicon/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjake/headliner/efe92de137bf522160c89c603ee180088405bbc3/pkgdown/favicon/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjake/headliner/efe92de137bf522160c89c603ee180088405bbc3/pkgdown/favicon/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjake/headliner/efe92de137bf522160c89c603ee180088405bbc3/pkgdown/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjake/headliner/efe92de137bf522160c89c603ee180088405bbc3/pkgdown/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjake/headliner/efe92de137bf522160c89c603ee180088405bbc3/pkgdown/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjake/headliner/efe92de137bf522160c89c603ee180088405bbc3/pkgdown/favicon/favicon.ico -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(headliner) 3 | 4 | test_check("headliner") 5 | -------------------------------------------------------------------------------- /tests/testthat/test-add-date-columns.R: -------------------------------------------------------------------------------- 1 | library(lubridate) 2 | 3 | test_that("add_date_columns works", { 4 | x <- ymd(20210101) 5 | df <- 6 | tibble::tibble( 7 | date = x %m+% months(-7:9) 8 | ) |> 9 | add_date_columns(date, ref_date = ymd(20210101)) 10 | 11 | expect_equal(dim(df), c(17, 7)) 12 | expect_equal(range(df$day), c(-214, 273)) 13 | expect_equal(range(df$week), c(-30, 39)) 14 | expect_equal(range(df$month), c(-7, 9)) 15 | expect_equal(range(df$quarter), c(-3, 3)) 16 | expect_equal(range(df$calendar_year), c(-1, 0)) 17 | expect_equal(range(df$fiscal_year), c(-1, 1)) 18 | 19 | df_positive <- 20 | df |> 21 | dplyr::mutate_if(is.numeric, ~(.x > 0)) 22 | 23 | expect_equal(sum(df_positive$day), 9) 24 | expect_equal(sum(df_positive$week), 9) 25 | expect_equal(sum(df_positive$month), 9) 26 | expect_equal(sum(df_positive$quarter), 7) 27 | expect_equal(sum(df_positive$calendar_year), 0) 28 | expect_equal(sum(df_positive$fiscal_year), 4) 29 | }) 30 | 31 | test_that("overlapping columns dropped or renamed", { 32 | df <- 33 | demo_data() |> 34 | dplyr::mutate(day = weekdays(date)) 35 | 36 | expect_warning(add_date_columns(df, date), "duplicate names") 37 | 38 | expect_equal( 39 | names(add_date_columns(df, date, drop = TRUE)), 40 | names(add_date_columns(demo_data(), date)) 41 | ) 42 | }) 43 | -------------------------------------------------------------------------------- /tests/testthat/test-add-headline-column.R: -------------------------------------------------------------------------------- 1 | test_that("add headline column works", { 2 | df <- add_headline_column(mtcars, gear, carb) 3 | 4 | df_new_col <- add_headline_column(mtcars, gear, carb, .name = "abc") 5 | 6 | expect_true("headline" %in% names(df)) 7 | expect_true("abc" %in% names(df_new_col)) 8 | expect_warning( 9 | add_headline_column(mtcars, gear, carb, .name = "mpg") 10 | ) 11 | }) 12 | 13 | test_that("add headline column returns columns", { 14 | df <- 15 | add_headline_column( 16 | df = mtcars, 17 | x = gear, 18 | y = carb, 19 | return_cols = dplyr::starts_with("delta") 20 | ) 21 | 22 | expect_true("headline" %in% names(df)) 23 | expect_true(all(c("delta", "delta_p") %in% names(df))) 24 | }) 25 | 26 | 27 | test_that("add headline can access other columns", { 28 | df <- 29 | add_headline_column( 30 | df = pixar_films, 31 | x = bo_domestic, 32 | y = bo_intl, 33 | headline = "{film} ({orig_values})" 34 | ) |> 35 | mutate(has_text = purrr::map2_lgl(film, headline, grepl)) 36 | 37 | expect_true(all(df$has_text)) 38 | }) 39 | 40 | 41 | test_that("add headline can pass '...", { 42 | df <- 43 | add_headline_column( 44 | df = pixar_films, 45 | x = bo_domestic, 46 | y = bo_intl, 47 | headline = "{abc} {film} {trend}", 48 | abc = "123" 49 | ) |> 50 | mutate(has_text = purrr::map2_lgl("^123", headline, grepl)) 51 | 52 | expect_true(all(df$has_text)) 53 | }) 54 | 55 | test_that("if_match works", { 56 | df <- 57 | data.frame( 58 | a = 1:3, 59 | b = 3:1 60 | ) |> 61 | add_headline_column(a, b) 62 | 63 | expect_equal( 64 | df$headline[2], 65 | formals(add_headline_column)[["if_match"]] 66 | ) 67 | }) 68 | 69 | test_that("warning if columns renamed", { 70 | data.frame( 71 | x = 1:3, 72 | y = 3:1 73 | ) |> 74 | add_headline_column( 75 | x = y, 76 | y = x, 77 | return_cols = everything() 78 | ) |> 79 | expect_message("New names") 80 | }) 81 | 82 | 83 | test_that("returns NA instead of error", { 84 | expect_error( 85 | object = { 86 | tibble(x = 1:4, y = c(3:1, NA)) |> 87 | add_headline_column(x, y) 88 | 89 | }, 90 | regexp = NA # no error 91 | ) 92 | }) 93 | 94 | test_that("uses dynamic headline", { 95 | headlines <- c( 96 | "{other} has {trend}d", 97 | "{delta_p}" 98 | ) 99 | 100 | df <- 101 | data.frame( 102 | a = 1:2, 103 | b = 2:3, 104 | other = "car" 105 | ) |> 106 | mutate(phrase = headlines) 107 | 108 | res <- 109 | df |> 110 | add_headline_column(a, b, phrase) 111 | 112 | expect_equal( 113 | res$headline, 114 | c("car has decreased", "33.3") 115 | ) 116 | }) 117 | 118 | test_that("lists passed correctly", { 119 | res <- 120 | data.frame( 121 | a = c(2, 5), 122 | b = 3 123 | ) |> 124 | add_headline_column( 125 | a, b, 126 | headline = "{delta} {more} {cat} ({higher})", 127 | trend_phrases = list( 128 | higher = trend_terms("higher", "lower"), 129 | more = trend_terms("more", "less") 130 | ), 131 | plural_phrases = list( 132 | cat = plural_phrasing("cat", "cats") 133 | ) 134 | ) 135 | expect_equal( 136 | res$headline, 137 | c("1 less cat (lower)", "2 more cats (higher)") 138 | ) 139 | }) 140 | -------------------------------------------------------------------------------- /tests/testthat/test-compare-conditions.R: -------------------------------------------------------------------------------- 1 | test_that("mean (default) works", { 2 | x <- 3 | pixar_films |> 4 | compare_conditions( 5 | x = (rating == "G"), 6 | y = TRUE, 7 | bo_domestic 8 | ) 9 | 10 | expect_equal(names(x), c("mean_bo_domestic_x", "mean_bo_domestic_y")) 11 | expect_equal( 12 | x$mean_bo_domestic_x, 13 | mean(pixar_films$bo_domestic[pixar_films$rating == "G"]) 14 | ) 15 | 16 | expect_equal( 17 | x$mean_bo_domestic_y, 18 | mean(pixar_films$bo_domestic) 19 | ) 20 | }) 21 | 22 | 23 | test_that("max works", { 24 | x <- 25 | pixar_films |> 26 | compare_conditions( 27 | x = (rating == "G"), 28 | y = TRUE, 29 | .cols = bo_domestic, 30 | .fns = list(max = max) 31 | ) 32 | 33 | expect_equal(names(x), c("max_bo_domestic_x", "max_bo_domestic_y")) 34 | expect_equal( 35 | x$max_bo_domestic_x, 36 | max(pixar_films$bo_domestic[pixar_films$rating == "G"]) 37 | ) 38 | 39 | expect_equal( 40 | x$max_bo_domestic_y, 41 | max(pixar_films$bo_domestic) 42 | ) 43 | }) 44 | 45 | 46 | test_that("grouped df vs non-grouped", { 47 | df <- 48 | compare_conditions( 49 | df = mtcars, 50 | .cols = mpg, 51 | .fns = mean 52 | ) 53 | 54 | df_grouped <- 55 | compare_conditions( 56 | df = dplyr::group_by(mtcars, cyl), 57 | .cols = mpg, 58 | .fns = mean 59 | ) 60 | 61 | expect_equal(dim(df), c(1, 2)) 62 | expect_equal(dim(df_grouped), c(3, 3)) 63 | }) 64 | -------------------------------------------------------------------------------- /tests/testthat/test-compare-values.R: -------------------------------------------------------------------------------- 1 | library(purrr) 2 | 3 | keep_numeric <- function(x) { 4 | x |> 5 | map(keep, is.numeric) |> 6 | compact() |> 7 | as.numeric() 8 | } 9 | 10 | 11 | test_that("compare_values produces list", { 12 | x <- compare_values(2, 10) 13 | 14 | expect_true(is.list(x)) 15 | 16 | expect_true(x$delta == 8) 17 | expect_true(x$raw_delta == -8) 18 | expect_true(x$article_delta == "an 8") 19 | expect_true(x$article_raw_delta == "a -8") 20 | 21 | expect_true( 22 | all( 23 | names(x) == c( 24 | "x", "y", 25 | "delta", "delta_p", 26 | "article_delta", "article_delta_p", 27 | "raw_delta", "raw_delta_p", 28 | "article_raw_delta", "article_raw_delta_p", 29 | "sign", "orig_values", "trend" 30 | ) 31 | ) 32 | ) 33 | }) 34 | 35 | 36 | test_that("multiplier works", { 37 | select_vars <- function(x) { 38 | view_list(x)[c("delta", "raw_delta", "comp_value", "ref_value"), 1] |> 39 | as.numeric() 40 | } 41 | 42 | whole_numbers <- select_vars(compare_values(23.4, 34.5)) 43 | multiplied <- select_vars(compare_values(0.234, 0.345, multiplier = 100)) 44 | decimal <- select_vars(compare_values(0.234, 0.345, n_decimal = 2)) 45 | 46 | expect_equal(whole_numbers, multiplied) 47 | expect_equal(round(multiplied / 100, 2), decimal) 48 | }) 49 | 50 | test_that("check rounding runs", { 51 | expect_message(compare_values(0.123, 0.12)) 52 | expect_message(compare_values(21.1, 21.12)) 53 | # no message 54 | expect_warning(compare_values(0.123, 0.1234, n_decimal = 4), regexp = NA) 55 | expect_warning(compare_values(0.12, 0.123, multiplier = 100), regexp = NA) 56 | }) 57 | 58 | 59 | test_that("compare_values accepts multiple trend terms", { 60 | trend_list <- 61 | list( 62 | when_more = trend_terms("more", "less"), 63 | when_higher = trend_terms("higher", "lower") 64 | ) 65 | 66 | x <- 67 | compare_values( 68 | 5, 4, 69 | trend_phrases = trend_list 70 | ) 71 | 72 | expect_equal(x$when_more, "more") 73 | expect_equal(x$when_higher, "higher") 74 | }) 75 | 76 | 77 | test_that("compare_values accepts multiple trend terms", { 78 | plural_list <- 79 | list( 80 | n_people = plural_phrasing("person", "people"), 81 | n_cats = plural_phrasing("cat", "cats") 82 | ) 83 | 84 | x <- 85 | compare_values( 86 | 5, 4, 87 | plural_phrases = plural_list 88 | ) 89 | 90 | expect_equal(x$n_people, "person") 91 | expect_equal(x$n_cats, "cat") 92 | }) 93 | 94 | 95 | test_that("return_trend_phrases() works", { 96 | test_direct <- trend_terms() 97 | test_list <- list(trend = trend_terms()) 98 | 99 | expect_equal( 100 | return_trend_phrases(test_direct), 101 | return_trend_phrases(test_list) 102 | ) 103 | }) 104 | 105 | 106 | test_that("returns a list of NA if NA", { 107 | with_value <- compare_values(1, 2) 108 | with_na_x <- compare_values(NA, 2) 109 | with_na_y <- compare_values(1, NA) 110 | expected <- rep(NA, length(with_value)) 111 | 112 | expect_equal(expected, unlist(with_na_x) |> unname()) 113 | expect_equal(expected, unlist(with_na_y) |> unname()) 114 | 115 | expect_equal(names(with_value), names(with_na_x)) 116 | expect_equal(names(with_value), names(with_na_y)) 117 | }) 118 | 119 | 120 | test_that("error if length > 1", { 121 | expect_error(compare_values(x = 1:2, y = 2 )) 122 | expect_error(compare_values(x = 1, y = 1:2)) 123 | }) 124 | -------------------------------------------------------------------------------- /tests/testthat/test-demo-data.R: -------------------------------------------------------------------------------- 1 | test_that("demo_data works", { 2 | x <- demo_data() 3 | n <- nrow(x) 4 | 5 | min_month <- min(seq.Date(Sys.Date(), length.out = n, by = "-2 month")) 6 | 7 | expect_equal(x$date[1], Sys.Date()) 8 | expect_equal(x$date[n], min_month) 9 | }) 10 | 11 | 12 | test_that("demo_data accepts params", { 13 | x <- demo_data(n = 2, by = "-1 day") 14 | 15 | expect_equal(x$date[1], Sys.Date()) 16 | expect_equal(x$date[2], Sys.Date() - 1) 17 | }) 18 | 19 | 20 | test_that("large n works", { 21 | (demo_data(n = 6)$group == rep(letters[1:3], each = 2)) |> 22 | all() |> 23 | expect_true() 24 | 25 | (unique(demo_data(n = 200)$group) == letters) |> 26 | all() |> 27 | expect_true() 28 | }) 29 | -------------------------------------------------------------------------------- /tests/testthat/test-headline.R: -------------------------------------------------------------------------------- 1 | test_that("vector works correctly", { 2 | expect_equal( 3 | headline(1, 3), 4 | "decrease of 2 (1 vs. 3)" 5 | ) 6 | }) 7 | 8 | 9 | test_that("list is passed correctly", { 10 | named <- 11 | list(a = 8, b = 9, c = 10) |> 12 | headline_list(x = c, y = a) 13 | 14 | unnamed <- 15 | list(10, 8) |> 16 | headline_list() 17 | 18 | phrase <- glue::glue("increase of 2 (10 vs. 8)") 19 | 20 | expect_equal(phrase, named) 21 | expect_equal(phrase, unnamed) 22 | }) 23 | 24 | test_that("check that list with multiple elements shows message", { 25 | expect_message( 26 | data.frame(a = 2, b = 3, c = 4) |> 27 | headline_list(), 28 | regexp = "two elements" 29 | ) 30 | }) 31 | 32 | test_that("phrases added", { 33 | phrases <- list( 34 | person = plural_phrasing("person", "people"), 35 | was = plural_phrasing("was", "were") 36 | ) 37 | when_multi <- headline(3, 1, plural_phrases = phrases, "{person} {was}") 38 | when_single <- headline(2, 1, plural_phrases = phrases, "{person} {was}") 39 | 40 | expect_true(when_multi == "people were") 41 | expect_true(when_single == "person was") 42 | }) 43 | 44 | 45 | 46 | test_that("list is returned", { 47 | x <- headline(1, 2, return_data = TRUE) 48 | 49 | expect_true(inherits(x, "list")) 50 | }) 51 | 52 | 53 | test_that("glue_data() allows extra expressions", { 54 | as_headline <- 55 | headline( 56 | 3, 2, 57 | "{trend} on {date}", 58 | date = "1/1/2022" 59 | ) 60 | 61 | as_list <- 62 | headline( 63 | 3, 2, 64 | "{trend} on {date}", 65 | date = "1/1/2022", 66 | return_data = TRUE 67 | ) 68 | 69 | expect_true( 70 | all( 71 | c(as_headline, as_list$headline) == "increase on 1/1/2022" 72 | ) 73 | ) 74 | }) 75 | 76 | 77 | test_that("is vectorized", { 78 | expect_equal( 79 | object = 80 | headline(1:3, 3:1) |> 81 | unique() |> 82 | length(), 83 | expected = 3 84 | ) 85 | }) 86 | 87 | 88 | test_that("returns NA instead of error", { 89 | expect_error( 90 | object = headline(c(NA, 1:2), 3:1), 91 | regexp = NA # no error 92 | ) 93 | }) 94 | 95 | 96 | test_that("defaults are the same across similar functions", { 97 | hl <- formals(headline) 98 | cv <- formals(compare_values) 99 | hc <- formals(add_headline_column) 100 | 101 | hl_cv <- intersect(names(hl), names(cv)) 102 | hl_hc <- intersect(names(hl), names(hc)) 103 | 104 | expect_equal( 105 | hl[hl_cv], 106 | cv[hl_cv] 107 | ) 108 | 109 | expect_equal( 110 | hl[hl_hc], 111 | hc[hl_hc] 112 | ) 113 | }) 114 | -------------------------------------------------------------------------------- /tests/testthat/test-plural-phrasing.R: -------------------------------------------------------------------------------- 1 | test_that("plural_phrases works", { 2 | expect_error( 3 | compare_values(10, 2, plural_phrases = list(plural_phrasing("a", "an"))), 4 | "named list" 5 | ) 6 | 7 | phrases <- list( 8 | person = plural_phrasing("person", "people"), 9 | was = plural_phrasing("was", "were") 10 | ) 11 | 12 | when_multi <- compare_values(3, 1, plural_phrases = phrases) 13 | when_single <- compare_values(2, 1, plural_phrases = phrases) 14 | 15 | expect_true(when_multi$person == "people") 16 | expect_true(when_single$person == "person") 17 | 18 | expect_true(when_multi$was == "were") 19 | expect_true(when_single$was == "was") 20 | }) 21 | 22 | -------------------------------------------------------------------------------- /tests/testthat/test-string-manipulation.R: -------------------------------------------------------------------------------- 1 | test_that("add_article() works", { 2 | expect_equal(add_article("increase"), "an increase") 3 | expect_equal(add_article("decrease"), "a decrease") 4 | expect_equal(add_article(11), "an 11") 5 | 6 | expect_equal( 7 | headline(3, 2, "there was {add_article(trend)}"), 8 | "there was an increase" 9 | ) 10 | }) 11 | 12 | -------------------------------------------------------------------------------- /tests/testthat/test-utils-data-manipulation.R: -------------------------------------------------------------------------------- 1 | test_that("check_overlapping_names() works", { 2 | expect_warning( 3 | object = check_overlapping_names(mtcars, mtcars[,1:3]), 4 | regexp = "duplicate names" 5 | ) 6 | 7 | # no warning/no overlap 8 | expect_warning( 9 | object = check_overlapping_names(mtcars, iris), 10 | regexp = NA 11 | ) 12 | }) 13 | 14 | 15 | test_that("aggregate_group() works", { 16 | x <- 17 | aggregate_group( 18 | df = mtcars, 19 | name = "_x", 20 | .cols = c(mpg, hp), 21 | .fns = list(avg = mean) 22 | ) 23 | 24 | expect_equal(names(x), c("avg_mpg_x", "avg_hp_x")) 25 | expect_equal(x$avg_mpg_x, mean(mtcars$mpg)) 26 | expect_equal(x$avg_hp_x, mean(mtcars$hp)) 27 | }) 28 | 29 | 30 | test_that("aggregate_group() allows grouped df", { 31 | x <- 32 | aggregate_group( 33 | df = dplyr::group_by(mtcars, cyl), 34 | name = "_x", 35 | .cols = mpg, 36 | .fns = mean 37 | ) 38 | 39 | expect_equal(dim(x), c(3, 2)) 40 | }) 41 | 42 | 43 | test_that("check rounding throws a message", { 44 | expect_null(check_rounding(0.2, 0.24, n_decimal = 2)) 45 | expect_null(check_rounding(0.21, 0.21, n_decimal = 1)) 46 | expect_message(check_rounding(0.2, 0.24, n_decimal = 1)) 47 | expect_message( 48 | check_rounding(x = c(1.9, 2.01), y = 2, n_decimal = 1), 49 | regexp = "input #2)" 50 | ) 51 | expect_message( 52 | check_rounding(x = c(2.01, 2.02, 2.1), y = 2, n_decimal = 1), 53 | regexp = "input #1 and 2)" 54 | ) 55 | # expect_message( 56 | # data.frame(new = 18:22/100, old = 0.2) |> 57 | # add_headline_column( 58 | # new, old, 59 | # return_cols = c(x, y) 60 | # ), 61 | # regexp = "input #1 and 2)" 62 | # ) 63 | }) 64 | 65 | 66 | test_that("get_article() works for characters", { 67 | expect_equal(get_article("decrease"), "a") 68 | expect_equal(get_article("increase"), "an") 69 | }) 70 | 71 | 72 | 73 | test_that("get_article() works for numbers", { 74 | is_a <- 75 | purrr::map_chr( 76 | c(-8, -6, -0.2, 0.123, 0:7, 9, 10, 12:17, 19:20, 199, 1234, 1000111, 12000111), 77 | get_article 78 | ) 79 | 80 | is_an <- 81 | purrr::map_chr( 82 | c(8, 80, 11, 18, 11800, 11000, 11000111), 83 | get_article 84 | ) 85 | 86 | expect_equal(unique(is_a), "a") 87 | expect_equal(unique(is_an), "an") 88 | 89 | }) 90 | 91 | -------------------------------------------------------------------------------- /tests/testthat/test-utils-dates.R: -------------------------------------------------------------------------------- 1 | library(lubridate) 2 | 3 | test_that("calc_distance works", { 4 | expect_equal( 5 | calc_distance(ymd(20210101) + (-3:3), "days", ymd(20210101)), 6 | -3:3 7 | ) 8 | expect_equal( 9 | calc_distance(ymd(20210115) %m+% months(-3:3), "months", ymd(20210101)), 10 | -3:3 11 | ) 12 | }) 13 | 14 | test_that("fiscal_date works", { 15 | expect_equal(fiscal_date(as.Date("2021-01-01"), offset = 0), ymd(20210101)) 16 | expect_equal(fiscal_date(as.Date("2021-01-01"), offset = 6), ymd(20210701)) 17 | }) 18 | -------------------------------------------------------------------------------- /tests/testthat/test-view-list.R: -------------------------------------------------------------------------------- 1 | test_that("view_list works", { 2 | x <- compare_values(10, 8) 3 | x_df <- view_list(x) 4 | 5 | expect_true(inherits(x_df, "data.frame")) 6 | 7 | expect_equal( 8 | length(x), 9 | nrow(view_list(x)) 10 | ) 11 | 12 | expect_equal( 13 | x$orig_values, 14 | x_df["orig_values", "value"] 15 | ) 16 | }) 17 | -------------------------------------------------------------------------------- /vignettes/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | *.R 3 | --------------------------------------------------------------------------------