├── .Rbuildignore ├── .gitattributes ├── .github ├── .gitignore ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── R-CMD-check.yaml │ ├── pkgdown.yaml │ ├── recheck.yml │ └── test-coverage.yaml ├── .gitignore ├── .vscode ├── settings.json └── tasks.json ├── CONDUCT.md ├── DESCRIPTION ├── LICENSE ├── LICENSE.md ├── NAMESPACE ├── NEWS.md ├── R ├── cat_plot.R ├── int_utils.R ├── interact_plot.R ├── johnson_neyman.R ├── simple_margins.R ├── simple_slopes.R └── utils.R ├── README.Rmd ├── README.md ├── _pkgdown.yml ├── appveyor.yml ├── cran-comments.md ├── hex.png ├── inst └── CITATION ├── make_hex.R ├── man ├── as_huxtable.sim_margins.Rd ├── as_huxtable.sim_slopes.Rd ├── cat_plot.Rd ├── figures │ ├── interact_plot_continuous-1.png │ ├── interact_plot_continuous_points-1.png │ ├── interact_plot_factor-1.png │ ├── j-n-plot-1.png │ └── logo.png ├── interact_plot.Rd ├── johnson_neyman.Rd ├── plot.sim_margins.Rd ├── plot.sim_slopes.Rd ├── probe_interaction.Rd ├── reexports.Rd ├── sim_margins.Rd ├── sim_margins_tidiers.Rd ├── sim_slopes.Rd └── sim_slopes_tidiers.Rd ├── pkgdown ├── extra.css └── 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 │ ├── _snaps │ ├── cat_plot │ │ ├── p0bar.svg │ │ ├── p0bari.svg │ │ ├── p0barpp.svg │ │ ├── p0pt.svg │ │ ├── p0pti.svg │ │ ├── p0ptpp.svg │ │ ├── p0ptps.svg │ │ ├── p3bar.svg │ │ ├── p3bari.svg │ │ ├── p3barpp.svg │ │ ├── p3barps.svg │ │ ├── p3line.svg │ │ ├── p3linei.svg │ │ ├── p3linelty.svg │ │ ├── p3linepp.svg │ │ ├── p3lineps.svg │ │ ├── p3pt.svg │ │ ├── p3pti.svg │ │ ├── p3ptpp.svg │ │ ├── p3ptps.svg │ │ ├── pcatbfit.svg │ │ ├── pglmcatoff.svg │ │ ├── plmbar-interact-plot.svg │ │ ├── plmbar.svg │ │ ├── plmbari.svg │ │ ├── plmbarpp.svg │ │ ├── plme4cat.svg │ │ ├── plmline.svg │ │ ├── plmlinei-interact-plot.svg │ │ ├── plmlinei.svg │ │ ├── plmlinelt.svg │ │ ├── plmlinepp.svg │ │ ├── plmlineps.svg │ │ ├── plmpt.svg │ │ ├── plmpti.svg │ │ ├── plmptpp.svg │ │ ├── plmptps-interact-plot.svg │ │ ├── plmptps.svg │ │ └── psvycat.svg │ └── interact_plot │ │ ├── pbfcat.svg │ │ ├── pbfcont.svg │ │ ├── pglmoff.svg │ │ ├── pglmrob.svg │ │ ├── plm0.svg │ │ ├── plm1.svg │ │ ├── plma.svg │ │ ├── plme4-interact-plot-to-cat-plot.svg │ │ ├── plme4.svg │ │ ├── plme4i.svg │ │ ├── plmlabelsc.svg │ │ ├── plmlabelsc2.svg │ │ ├── plmlabelscpred-interact-plot-to-cat-plot.svg │ │ ├── plmlabelscpred.svg │ │ ├── plmlabelscs.svg │ │ ├── plmlinearchnp.svg │ │ ├── plmlinearchp.svg │ │ ├── plmm.svg │ │ ├── plmnfchar.svg │ │ ├── plmrob.svg │ │ ├── plmrugb.svg │ │ ├── plmruglb.svg │ │ ├── plmtercs.svg │ │ ├── plmtf.svg │ │ ├── plmw.svg │ │ ├── prsacont.svg │ │ ├── psvy1.svg │ │ └── psvya.svg │ ├── brmfit.rds │ ├── rsafit.rds │ ├── test_cat_plot.R │ ├── test_interact_plot.R │ ├── test_sim_margins.R │ └── test_sim_slopes.R └── vignettes ├── categorical.Rmd └── interactions.Rmd /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^CRAN-RELEASE$ 2 | ^.*\.Rproj$ 3 | ^\.Rproj\.user$ 4 | ^\.travis\.yml$ 5 | ^appveyor\.yml$ 6 | ^README\.Rmd$ 7 | ^CONDUCT\.md$ 8 | ^cran-comments\.md$ 9 | ^docs$ 10 | ^.*\.sublime-project$ 11 | ^.*\.sublime-workspace$ 12 | ^_pkgdown.yaml$ 13 | ^download_count.R$ 14 | ^misc$ 15 | ^\.vscode$ 16 | ^revdep$ 17 | ^_pkgdown\.yml$ 18 | ^doc$ 19 | ^Meta$ 20 | ^make_hex\.R$ 21 | ^hex\.png$ 22 | ^LICENSE\.md$ 23 | ^\.github$ 24 | ^pkgdown$ 25 | ^interactions\.Rproj$ 26 | ^CRAN-SUBMISSION$ 27 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Let me know if something isn't working 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | 13 | 14 | Brief description of the problem 15 | 16 | ```r 17 | # insert reprex here 18 | ``` 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | 12 | 13 | **Describe the solution you'd like** 14 | 15 | 16 | **Describe any alternatives or other implementations that you might know of** 17 | 18 | 19 | **Additional context** 20 | 21 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.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: R-CMD-check 10 | 11 | permissions: read-all 12 | 13 | jobs: 14 | R-CMD-check: 15 | runs-on: ${{ matrix.config.os }} 16 | 17 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 18 | 19 | strategy: 20 | fail-fast: false 21 | matrix: 22 | config: 23 | - {os: macos-latest, r: 'release'} 24 | - {os: windows-latest, r: 'release'} 25 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} 26 | - {os: ubuntu-latest, r: 'release'} 27 | - {os: ubuntu-latest, r: 'oldrel-1'} 28 | 29 | env: 30 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 31 | R_KEEP_PKG_SOURCE: yes 32 | 33 | steps: 34 | - uses: actions/checkout@v4 35 | 36 | - uses: r-lib/actions/setup-pandoc@v2 37 | 38 | - uses: r-lib/actions/setup-r@v2 39 | with: 40 | r-version: ${{ matrix.config.r }} 41 | http-user-agent: ${{ matrix.config.http-user-agent }} 42 | use-public-rspm: true 43 | 44 | - uses: r-lib/actions/setup-r-dependencies@v2 45 | with: 46 | extra-packages: any::rcmdcheck 47 | needs: check 48 | 49 | - uses: r-lib/actions/check-r-package@v2 50 | with: 51 | upload-snapshots: true 52 | build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")' 53 | -------------------------------------------------------------------------------- /.github/workflows/pkgdown.yaml: -------------------------------------------------------------------------------- 1 | # Jacob copied from https://github.com/r-lib/pkgdown/blob/main/.github/workflows/pkgdown.yaml 2 | # Workflow derived from https://github.com/r-lib/actions/tree/master/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 | tags: ['*'] 8 | pull_request: 9 | branches: [master, main] 10 | 11 | name: pkgdown 12 | 13 | jobs: 14 | pkgdown: 15 | if: github.event_name == 'push' || github.event.pull_request.head.repo.fork == false 16 | runs-on: ubuntu-latest 17 | env: 18 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 19 | NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} 20 | NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} 21 | isPush: ${{ github.event_name == 'push' }} 22 | 23 | steps: 24 | - uses: actions/checkout@v3 25 | 26 | - uses: r-lib/actions/setup-tinytex@v2 27 | 28 | - uses: r-lib/actions/setup-pandoc@v2 29 | 30 | - uses: r-lib/actions/setup-r@v2 31 | with: 32 | use-public-rspm: true 33 | 34 | - uses: r-lib/actions/setup-r-dependencies@v2 35 | with: 36 | extra-packages: any::pkgdown, local::. 37 | needs: website 38 | 39 | - name: Install package 40 | run: R CMD INSTALL . 41 | 42 | - name: Build and deploy pkgdown site 43 | if: contains(env.isPush, 'true') 44 | run: | 45 | git config --local user.name "$GITHUB_ACTOR" 46 | git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com" 47 | Rscript -e 'pkgdown::deploy_to_branch(new_process = FALSE)' 48 | - name: Create website 49 | if: contains(env.isPush, 'false') 50 | run: | 51 | pkgdown::build_site() 52 | shell: Rscript {0} 53 | 54 | - name: Create index file 55 | if: contains(env.isPush, 'false') 56 | run: | 57 | echo ' ' > ./docs/index.html 58 | - name: Deploy to Netlify 59 | if: contains(env.isPush, 'false') 60 | id: netlify-deploy 61 | uses: nwtgck/actions-netlify@v1.1 62 | with: 63 | publish-dir: './docs' 64 | production-branch: master 65 | github-token: ${{ secrets.GITHUB_TOKEN }} 66 | deploy-message: 67 | 'Deploy from GHA: ${{ github.event.pull_request.title || github.event.head_commit.message }} (${{ github.sha }})' 68 | # these all default to 'true' 69 | enable-pull-request-comment: false 70 | enable-commit-comment: false 71 | # enable-commit-status: true 72 | #o verwrites-pull-request-comment: true 73 | timeout-minutes: 1 74 | -------------------------------------------------------------------------------- /.github/workflows/recheck.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_dispatch: 3 | inputs: 4 | which: 5 | type: choice 6 | description: Which dependents to check 7 | options: 8 | - strong 9 | - most 10 | 11 | name: Reverse dependency check 12 | 13 | jobs: 14 | revdep_check: 15 | name: Reverse check ${{ inputs.which }} dependents 16 | uses: r-devel/recheck/.github/workflows/recheck.yml@v1 17 | with: 18 | which: ${{ inputs.which }} 19 | -------------------------------------------------------------------------------- /.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 | permissions: read-all 12 | 13 | jobs: 14 | test-coverage: 15 | runs-on: ubuntu-latest 16 | env: 17 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | 22 | - uses: r-lib/actions/setup-r@v2 23 | with: 24 | use-public-rspm: true 25 | 26 | - uses: r-lib/actions/setup-r-dependencies@v2 27 | with: 28 | extra-packages: any::covr, any::xml2 29 | needs: coverage 30 | 31 | - name: Test coverage 32 | run: | 33 | cov <- covr::package_coverage( 34 | quiet = FALSE, 35 | clean = FALSE, 36 | install_path = file.path(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "package") 37 | ) 38 | covr::to_cobertura(cov) 39 | shell: Rscript {0} 40 | 41 | - uses: codecov/codecov-action@v4 42 | with: 43 | fail_ci_if_error: ${{ github.event_name != 'pull_request' && true || false }} 44 | file: ./cobertura.xml 45 | plugin: noop 46 | disable_search: true 47 | token: ${{ secrets.CODECOV_TOKEN }} 48 | 49 | - name: Show testthat output 50 | if: always() 51 | run: | 52 | ## -------------------------------------------------------------------- 53 | find '${{ runner.temp }}/package' -name 'testthat.Rout*' -exec cat '{}' \; || true 54 | shell: bash 55 | 56 | - name: Upload test results 57 | if: failure() 58 | uses: actions/upload-artifact@v4 59 | with: 60 | name: coverage-test-failures 61 | path: ${{ runner.temp }}/package 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # History files 2 | .Rhistory 3 | .Rapp.history 4 | # Session Data files 5 | .RData 6 | # Example code in package build process 7 | *-Ex.R 8 | # Output files from R CMD build 9 | /*.tar.gz 10 | # Output files from R CMD check 11 | /*.Rcheck/ 12 | # RStudio files 13 | *.Rproj.user/ 14 | *.Rproj 15 | # produced vignettes 16 | vignettes/*.html 17 | vignettes/*.pdf 18 | # OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3 19 | .httr-oauth 20 | # knitr and R markdown default cache directories 21 | /*_cache/ 22 | /cache/ 23 | # Temporary files created by R markdown 24 | *.utf8.md 25 | *.knit.md 26 | .Rproj.user 27 | inst/doc 28 | Simple slopes macro replication.Rmd 29 | Simple slopes macro replication.nb.html 30 | 31 | *.Rhistory 32 | 33 | jtools\.sublime-project 34 | 35 | jtools\.sublime-workspace 36 | 37 | docs/ 38 | 39 | tests/testthat/Rplots\.pdf 40 | 41 | CRAN-RELEASE 42 | doc 43 | Meta 44 | 45 | .DS_Store 46 | .vscode/launch.json 47 | /doc/ 48 | /Meta/ 49 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "r.lintr.enabled": false 3 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "R CMD check", 8 | "type": "shell", 9 | "command": "R --no-restore-data -e 'devtools::check()'", 10 | "group": "test", 11 | "presentation": { 12 | "echo": true, 13 | "reveal": "always", 14 | "focus": false, 15 | "panel": "shared" 16 | } 17 | }, 18 | { 19 | "label": "Test package", 20 | "type": "shell", 21 | "command": "R --no-restore-data -e devtools::test() -e q()", 22 | "group": "test", 23 | "presentation": { 24 | "echo": true, 25 | "reveal": "always", 26 | "focus": false, 27 | "panel": "shared" 28 | }, 29 | "problemMatcher": [] 30 | } 31 | ] 32 | } -------------------------------------------------------------------------------- /CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at jacob.long@sc.edu. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct/ 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: interactions 2 | Type: Package 3 | Title: Comprehensive, User-Friendly Toolkit for Probing Interactions 4 | Version: 1.2.0 5 | Authors@R: person(c("Jacob","A."), "Long", email = "jacob.long@sc.edu", 6 | role = c("aut", "cre"), comment = c(ORCID = "0000-0002-1582-6214")) 7 | Description: A suite of functions for conducting and interpreting analysis 8 | of statistical interaction in regression models that was formerly part of the 9 | 'jtools' package. Functionality includes visualization of two- and three-way 10 | interactions among continuous and/or categorical variables as well as 11 | calculation of "simple slopes" and Johnson-Neyman intervals (see e.g., 12 | Bauer & Curran, 2005 ). These 13 | capabilities are implemented for generalized linear models in addition to the 14 | standard linear regression context. 15 | URL: https://interactions.jacob-long.com 16 | BugReports: https://github.com/jacob-long/interactions/issues 17 | License: MIT + file LICENSE 18 | Encoding: UTF-8 19 | Imports: 20 | ggplot2 (>= 3.4.0), 21 | cli, 22 | generics, 23 | broom, 24 | jtools (>= 2.0.3), 25 | rlang (>= 0.3.0), 26 | tibble 27 | Suggests: 28 | cowplot, 29 | broom.mixed, 30 | glue, 31 | huxtable (>= 3.0.0), 32 | lme4, 33 | margins, 34 | sandwich, 35 | survey, 36 | knitr, 37 | rmarkdown, 38 | testthat, 39 | vdiffr 40 | Enhances: 41 | brms, 42 | rstanarm 43 | VignetteBuilder: knitr 44 | Roxygen: list(markdown = TRUE) 45 | RoxygenNote: 7.3.2.9000 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2024 2 | COPYRIGHT HOLDER: Jacob A. Long 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2024 Jacob A. Long 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 | 4 | if (getRversion() >= "3.6.0") { 5 | S3method(huxtable::as_huxtable, sim_margins) 6 | } else { 7 | export(as_huxtable.sim_margins) 8 | } 9 | 10 | if (getRversion() >= "3.6.0") { 11 | S3method(huxtable::as_huxtable, sim_slopes) 12 | } else { 13 | export(as_huxtable.sim_slopes) 14 | } 15 | S3method(glance,sim_margins) 16 | S3method(glance,sim_slopes) 17 | S3method(nobs,sim_margins) 18 | S3method(nobs,sim_slopes) 19 | S3method(plot,sim_margins) 20 | S3method(plot,sim_slopes) 21 | S3method(print,johnson_neyman) 22 | S3method(print,probe_interaction) 23 | S3method(print,sim_margins) 24 | S3method(print,sim_slopes) 25 | S3method(tidy,sim_margins) 26 | S3method(tidy,sim_slopes) 27 | export(cat_plot) 28 | export(glance) 29 | export(interact_plot) 30 | export(johnson_neyman) 31 | export(probe_interaction) 32 | export(sim_margins) 33 | export(sim_slopes) 34 | export(tidy) 35 | import(ggplot2) 36 | import(jtools) 37 | import(rlang) 38 | importFrom(cli,cat_rule) 39 | importFrom(cli,rule) 40 | importFrom(generics,glance) 41 | importFrom(generics,tidy) 42 | importFrom(stats,aggregate) 43 | importFrom(stats,approx) 44 | importFrom(stats,as.formula) 45 | importFrom(stats,coef) 46 | importFrom(stats,coefficients) 47 | importFrom(stats,complete.cases) 48 | importFrom(stats,contrasts) 49 | importFrom(stats,df.residual) 50 | importFrom(stats,ecdf) 51 | importFrom(stats,family) 52 | importFrom(stats,formula) 53 | importFrom(stats,getCall) 54 | importFrom(stats,lm) 55 | importFrom(stats,median) 56 | importFrom(stats,model.frame) 57 | importFrom(stats,model.offset) 58 | importFrom(stats,nobs) 59 | importFrom(stats,predict) 60 | importFrom(stats,pt) 61 | importFrom(stats,qnorm) 62 | importFrom(stats,qt) 63 | importFrom(stats,quantile) 64 | importFrom(stats,relevel) 65 | importFrom(stats,residuals) 66 | importFrom(stats,sd) 67 | importFrom(stats,terms) 68 | importFrom(stats,update) 69 | importFrom(stats,update.formula) 70 | importFrom(stats,vcov) 71 | importFrom(stats,weighted.mean) 72 | importFrom(utils,methods) 73 | -------------------------------------------------------------------------------- /R/utils.R: -------------------------------------------------------------------------------- 1 | ## Quicker way to get last item of vector 2 | last <- function(x) {return(x[length(x)])} 3 | ## Just so code reads more clearly when using last(x) 4 | first <- function(x) {return(x[1])} 5 | 6 | # Get levels if they exist, otherwise unique 7 | ulevels <- function(x) { 8 | if (!is.null(levels(x))) { 9 | return(levels(x)) 10 | } else { 11 | if (!is.numeric(x)) { 12 | return(unique(x)) 13 | } else { 14 | return(sort(unique(x))) 15 | } 16 | } 17 | } 18 | 19 | make_ci_labs <- function(ci.width) { 20 | 21 | alpha <- (1 - ci.width) / 2 22 | 23 | lci_lab <- 0 + alpha 24 | lci_lab <- paste(round(lci_lab * 100, 1), "%", sep = "") 25 | 26 | uci_lab <- 1 - alpha 27 | uci_lab <- paste(round(uci_lab * 100, 1), "%", sep = "") 28 | 29 | list(lci = lci_lab, uci = uci_lab) 30 | 31 | } 32 | 33 | 34 | mean_or_base <- function(x, weights = NA) { 35 | if (is.numeric(x)) { 36 | if (all(is.na(weights))) { 37 | mean(x, na.rm = TRUE) 38 | } else { 39 | weighted.mean(x, weights, na.rm = TRUE) 40 | } 41 | } else if (!is.logical(x)) { 42 | levels(factor(x))[1] 43 | } else { 44 | FALSE 45 | } 46 | } 47 | 48 | ## Taken from panelr for handling non-synactic variable names 49 | bt <- function(x) { 50 | if (!is.null(x)) { 51 | btv <- paste0("`", x, "`") 52 | btv <- gsub("``", "`", btv, fixed = TRUE) 53 | btv <- btv %not% c("", "`") 54 | } else btv <- NULL 55 | return(btv) 56 | } 57 | 58 | un_bt <- function(x) { 59 | gsub("`", "", x) 60 | } 61 | 62 | # bt_if_needed <- function(string) { 63 | # if (make.names(string) != string) { 64 | # return(bt(string)) 65 | # } else { 66 | # return(string) 67 | # } 68 | # } 69 | 70 | ## Taken from Hmisc package to avoid importing for a minor feature 71 | ## Added "levels.median" 72 | #' @importFrom stats approx 73 | #' 74 | cut2 <- function(x, cuts, m = 150, g, levels.mean = FALSE, 75 | levels.median = FALSE, digits, 76 | minmax = TRUE, oneval = TRUE, onlycuts = FALSE) { 77 | method <- 1 78 | x.unique <- sort(unique(c(x[!is.na(x)], if (!missing(cuts)) cuts))) 79 | min.dif <- min(diff(x.unique))/2 80 | min.dif.factor <- 1 81 | if (missing(digits)) 82 | digits <- if (levels.mean) 83 | 5 84 | else 3 85 | oldopt <- options("digits") 86 | options(digits = digits) 87 | on.exit(options(oldopt)) 88 | xlab <- attr(x, "label") 89 | if (missing(cuts)) { 90 | nnm <- sum(!is.na(x)) 91 | if (missing(g)) 92 | g <- max(1, floor(nnm/m)) 93 | if (g < 1) 94 | stop("g must be >=1, m must be positive") 95 | options(digits = 15) 96 | n <- table(x) 97 | xx <- as.double(names(n)) 98 | options(digits = digits) 99 | cum <- cumsum(n) 100 | m <- length(xx) 101 | y <- as.integer(ifelse(is.na(x), NA, 1)) 102 | labs <- character(g) 103 | cuts <- approx(cum, xx, xout = (1:g) * nnm/g, method = "constant", 104 | rule = 2, f = 1)$y 105 | cuts[length(cuts)] <- max(xx) 106 | lower <- xx[1] 107 | upper <- 1e+45 108 | up <- low <- double(g) 109 | i <- 0 110 | for (j in 1:g) { 111 | cj <- if (method == 1 || j == 1) 112 | cuts[j] 113 | else { 114 | if (i == 0) 115 | stop("program logic error") 116 | s <- if (is.na(lower)) 117 | FALSE 118 | else xx >= lower 119 | cum.used <- if (all(s)) 120 | 0 121 | else max(cum[!s]) 122 | if (j == m) 123 | max(xx) 124 | else if (sum(s) < 2) 125 | max(xx) 126 | else approx(cum[s] - cum.used, xx[s], xout = (nnm - 127 | cum.used)/(g - j + 1), 128 | method = "constant", 129 | rule = 2, f = 1)$y 130 | } 131 | if (cj == upper) 132 | next 133 | i <- i + 1 134 | upper <- cj 135 | y[x >= (lower - min.dif.factor * min.dif)] <- i 136 | low[i] <- lower 137 | lower <- if (j == g) 138 | upper 139 | else min(xx[xx > upper]) 140 | if (is.na(lower)) 141 | lower <- upper 142 | up[i] <- lower 143 | } 144 | low <- low[1:i] 145 | up <- up[1:i] 146 | variation <- logical(i) 147 | for (ii in 1:i) { 148 | r <- range(x[y == ii], na.rm = TRUE) 149 | variation[ii] <- diff(r) > 0 150 | } 151 | if (onlycuts) 152 | return(unique(c(low, max(xx)))) 153 | flow <- format(low) 154 | fup <- format(up) 155 | bb <- c(rep(")", i - 1), "]") 156 | labs <- ifelse(low == up | (oneval & !variation), flow, 157 | paste("[", flow, ",", fup, bb, sep = "")) 158 | ss <- y == 0 & !is.na(y) 159 | if (any(ss)) 160 | stop_wrap("categorization error in cut2. Values of x not appearing in 161 | any interval:", paste(format(x[ss], digits = 12), 162 | collapse = " "), 163 | "Lower endpoints:", paste(format(low, digits = 12), 164 | collapse = " "), 165 | "\nUpper endpoints:", paste(format(up, digits = 12), 166 | collapse = " ")) 167 | y <- structure(y, class = "factor", levels = labs) 168 | } else { 169 | if (minmax) { 170 | r <- range(x, na.rm = TRUE) 171 | if (r[1] < cuts[1]) 172 | cuts <- c(r[1], cuts) 173 | if (r[2] > max(cuts)) 174 | cuts <- c(cuts, r[2]) 175 | } 176 | l <- length(cuts) 177 | k2 <- cuts - min.dif 178 | k2[l] <- cuts[l] 179 | y <- cut(x, k2) 180 | if (!levels.mean) { 181 | brack <- rep(")", l - 1) 182 | brack[l - 1] <- "]" 183 | fmt <- format(cuts) 184 | labs <- paste("[", fmt[1:(l - 1)], ",", fmt[2:l], 185 | brack, sep = "") 186 | if (oneval) { 187 | nu <- table(cut(x.unique, k2)) 188 | if (length(nu) != length(levels(y))) 189 | stop("program logic error") 190 | levels(y) <- ifelse(nu == 1, c(fmt[1:(l - 2)], 191 | fmt[l]), labs) 192 | } 193 | else levels(y) <- labs 194 | } 195 | } 196 | if (levels.mean) { 197 | means <- tapply(x, y, function(w) mean(w, na.rm = TRUE)) 198 | levels(y) <- format(means) 199 | } else if (levels.median) { 200 | medians <- tapply(x, y, function(w) median(w, na.rm = TRUE)) 201 | levels(y) <- format(medians) 202 | } 203 | attr(y, "class") <- "factor" 204 | # if (length(xlab)) 205 | # label(y) <- xlab 206 | y 207 | } 208 | 209 | # Some shorthand functions to automatically exclude NA 210 | quant <- function(x, ...) { 211 | quantile(x, ..., na.rm = TRUE) 212 | } 213 | min2 <- function(...) { 214 | min(..., na.rm = TRUE) 215 | } 216 | max2 <- function(...) { 217 | max(..., na.rm = TRUE) 218 | } 219 | 220 | # Avoiding unnecessary import of scales --- this is scales::squish 221 | squish <- function(x, range = c(0, 1), only.finite = TRUE) { 222 | force(range) 223 | finite <- if (only.finite) 224 | is.finite(x) 225 | else TRUE 226 | x[finite & x < range[1]] <- range[1] 227 | x[finite & x > range[2]] <- range[2] 228 | x 229 | } 230 | 231 | #'@export 232 | #'@importFrom generics tidy 233 | generics::tidy 234 | 235 | #'@export 236 | #'@importFrom generics glance 237 | generics::glance 238 | 239 | ### Hadley update ############################################################# 240 | # modified from https://stackoverflow.com/questions/13690184/update-inside-a-function- 241 | # only-searches-the-global-environment 242 | #' @importFrom stats update.formula 243 | 244 | j_update <- function(mod, formula = NULL, data = NULL, offset = NULL, 245 | weights = NULL, call.env = parent.frame(), ...) { 246 | call <- getCall(mod) 247 | if (is.null(call)) { 248 | stop("Model object does not support updating (no call)", call. = FALSE) 249 | } 250 | term <- terms(mod) 251 | if (is.null(term)) { 252 | stop("Model object does not support updating (no terms)", call. = FALSE) 253 | } 254 | 255 | if (!is.null(data)) call$data <- data 256 | if (!is.null(formula)) call$formula <- update.formula(call$formula, formula) 257 | env <- attr(term, ".Environment") 258 | # Jacob add 259 | # if (!is.null(offset)) 260 | call$offset <- offset 261 | # if (!is.null(weights)) 262 | call$weights <- weights 263 | 264 | 265 | extras <- as.list(match.call())[-1] 266 | extras <- extras[which(names(extras) %nin% c("mod", "formula", "data", 267 | "offset", "weights", 268 | "call.env"))] 269 | for (i in seq_along(extras)) { 270 | if (is.name(extras[[i]])) { 271 | extras[[i]] <- eval(extras[[i]], envir = call.env) 272 | } 273 | } 274 | 275 | existing <- !is.na(match(names(extras), names(call))) 276 | for (a in names(extras)[existing]) call[[a]] <- extras[[a]] 277 | if (any(!existing)) { 278 | call <- c(as.list(call), extras[!existing]) 279 | call <- as.call(call) 280 | } 281 | 282 | if (is.null(call.env)) {call.env <- parent.frame()} 283 | 284 | eval(call, env, call.env) 285 | } 286 | 287 | # adapted from https://stackoverflow.com/a/42742370 288 | # Looking for whether a method is defined for a given object (...) 289 | # getS3method() doesn't work for something like merMod because the string 290 | # "merMod" is not in the vector returned by class() 291 | #' @importFrom utils methods 292 | check_method <- function(generic, ...) { 293 | ch <- deparse(substitute(generic)) 294 | f <- X <- function(x, ...) UseMethod("X") 295 | for(m in methods(ch)) assign(sub(ch, "X", m, fixed = TRUE), "body<-"(f, value = m)) 296 | tryCatch({ 297 | X(...) 298 | TRUE 299 | }, error = function(e) { 300 | FALSE 301 | }) 302 | } 303 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | --- 4 | 5 | 6 | 7 | ```{r, echo = FALSE} 8 | knitr::opts_chunk$set( 9 | collapse = FALSE, 10 | comment = "#>", 11 | fig.path = "man/figures/", 12 | fig.width = 6.5, 13 | fig.height = 4, 14 | #render = knitr::normal_print, 15 | #dev.args=list(type="cairo"), 16 | # dev = "png", 17 | dpi = 300, 18 | retina = 1 19 | ) 20 | library(jtools) 21 | ``` 22 | 23 | # interactions interactions 24 | 25 | 26 | [![CRAN_Status_Badge](https://www.r-pkg.org/badges/version-ago/interactions)](https://cran.r-project.org/package=interactions) 27 | [![GitHub tag](https://img.shields.io/github/tag/jacob-long/interactions.svg?label=Github)](https://github.com/jacob-long/interactions) 28 | [![Total Downloads](https://cranlogs.r-pkg.org/badges/grand-total/interactions)](https://cran.r-project.org/package=interactions) 29 | [![R-CMD-check](https://github.com/jacob-long/interactions/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/jacob-long/interactions/actions/workflows/R-CMD-check.yaml) 30 | [![AppVeyor Build status](https://ci.appveyor.com/api/projects/status/qfyn5cwomufqxath?svg=true)](https://ci.appveyor.com/project/jacob-long/interactions) 31 | [![codecov](https://codecov.io/gh/jacob-long/interactions/branch/master/graph/badge.svg)](https://app.codecov.io/gh/jacob-long/interactions) 32 | [![Project Status: Active - The project has reached a stable, usable state and is being actively developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active) [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://opensource.org/licenses/MIT) 33 | 34 | 35 | 36 | This package consists of a number of tools for the analysis and 37 | interpretation of statistical interactions in regression models. Some of these 38 | features, especially those that pertain to visualization, are not highly 39 | labor-intensive to do oneself but are tedious and error-prone when done 40 | "by hand." 41 | 42 | Quick rundown of features: 43 | 44 | * simple slopes analysis 45 | * calculation of Johnson-Neyman intervals 46 | * visualization of predicted and observed values using `ggplot2` 47 | 48 | All of these are implemented in a consistent interface designed to be as 49 | simple as possible with tweaks and guts available to advanced users. 50 | GLMs, models from the `survey` package, and multilevel models from `lme4` 51 | are fully supported as is visualization for Bayesian models from `rstanaram` 52 | and `brms`. Several other model types work "out of the box" even though they 53 | are not officially supported. 54 | 55 | 56 | ## Installation 57 | 58 | The package is available via CRAN. 59 | 60 | ```r 61 | install.packages("interactions") 62 | ``` 63 | 64 | ## Usage 65 | 66 | Unless you have a keen eye and good familiarity with both the 67 | underlying mathematics and the scale of your variables, it can be very 68 | difficult to look at the output of regression model that includes an 69 | interaction and completely understand what the model is telling you. 70 | 71 | This package contains several means of aiding the understanding of and 72 | doing statistical inference with interactions. 73 | 74 | ### Johnson-Neyman intervals and simple slopes analysis 75 | 76 | The "classic" way of probing an interaction effect is to calculate the 77 | slope of the focal predictor at different values of the moderator. When 78 | the moderator is categorical, this is especially informative---e.g., what is 79 | the slope for cats vs. dogs? But you can also arbitrarily choose points 80 | for continuous moderators. 81 | 82 | With that said, the more statistically rigorous way to explore these effects 83 | is to find the Johnson-Neyman interval, which tells you the range of values 84 | of the moderator in which the slope of the predictor is significant vs. 85 | nonsignificant at a specified alpha level. 86 | 87 | The `sim_slopes` function will by default find the Johnson-Neyman interval 88 | and tell you the predictor's slope at specified values of the moderator; 89 | by default either both values of binary predictors or the mean and the 90 | mean +/- one standard deviation for continuous moderators. 91 | 92 | ```{r j-n-plot} 93 | library(interactions) 94 | fiti <- lm(mpg ~ hp * wt, data = mtcars) 95 | sim_slopes(fiti, pred = hp, modx = wt, jnplot = TRUE) 96 | ``` 97 | 98 | The Johnson-Neyman plot can help you get a handle on what the interval 99 | is telling you, too. Note that you can look at the Johnson-Neyman interval 100 | directly with the `johnson_neyman()` function. 101 | 102 | The above all generalize to three-way interactions, too, although 103 | Johnson-Neyman intervals do not handle the second moderator in the way that 104 | they do the first. 105 | 106 | ### Visualizing interaction effects 107 | 108 | This function plots two- and three-way interactions using `ggplot2` with a 109 | similar interface to the aforementioned `sim_slopes` function. Users can 110 | customize the appearance with familiar `ggplot2` commands. It supports several 111 | customizations, like confidence intervals. 112 | 113 | ```{r interact_plot_continuous} 114 | interact_plot(fiti, pred = hp, modx = wt, interval = TRUE) 115 | ``` 116 | 117 | You can also plot the observed data for comparison: 118 | 119 | ```{r interact_plot_continuous_points} 120 | interact_plot(fiti, pred = hp, modx = wt, plot.points = TRUE) 121 | ``` 122 | 123 | The function also supports categorical moderators---plotting observed data 124 | in these cases can reveal striking patterns. 125 | 126 | ```{r interact_plot_factor} 127 | fitiris <- lm(Petal.Length ~ Petal.Width * Species, data = iris) 128 | interact_plot(fitiris, pred = Petal.Width, modx = Species, plot.points = TRUE) 129 | ``` 130 | 131 | You may also combine the plotting and simple slopes functions by using 132 | `probe_interaction`, which calls both functions simultaneously. Categorical by 133 | categorical interactions can be investigated using the `cat_plot()` function. 134 | 135 | ## Contributing 136 | 137 | I'm happy to receive bug reports, suggestions, questions, and (most of all) 138 | contributions to fix problems and add features. I prefer you use the Github 139 | issues system over trying to reach out to me in other ways. Pull requests for 140 | contributions are encouraged. 141 | 142 | Please note that this project is released with a 143 | [Contributor Code of Conduct](https://github.com/jacob-long/interactions/blob/master/CONDUCT.md). 144 | By participating in this project you agree to abide by its terms. 145 | 146 | ## License 147 | 148 | The source code of this package is licensed under the 149 | [MIT License](https://opensource.org/licenses/mit-license.php). 150 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # interactions interactions 5 | 6 | 7 | 8 | [![CRAN_Status_Badge](https://www.r-pkg.org/badges/version-ago/interactions)](https://cran.r-project.org/package=interactions) 9 | [![GitHub 10 | tag](https://img.shields.io/github/tag/jacob-long/interactions.svg?label=Github)](https://github.com/jacob-long/interactions) 11 | [![Total 12 | Downloads](https://cranlogs.r-pkg.org/badges/grand-total/interactions)](https://cran.r-project.org/package=interactions) 13 | [![R-CMD-check](https://github.com/jacob-long/interactions/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/jacob-long/interactions/actions/workflows/R-CMD-check.yaml) 14 | [![AppVeyor Build 15 | status](https://ci.appveyor.com/api/projects/status/qfyn5cwomufqxath?svg=true)](https://ci.appveyor.com/project/jacob-long/interactions) 16 | [![codecov](https://codecov.io/gh/jacob-long/interactions/branch/master/graph/badge.svg)](https://app.codecov.io/gh/jacob-long/interactions) 17 | [![Project Status: Active - The project has reached a stable, usable 18 | state and is being actively 19 | developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active) 20 | [![MIT 21 | License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://opensource.org/licenses/MIT) 22 | 23 | 24 | This package consists of a number of tools for the analysis and 25 | interpretation of statistical interactions in regression models. Some of 26 | these features, especially those that pertain to visualization, are not 27 | highly labor-intensive to do oneself but are tedious and error-prone 28 | when done “by hand.” 29 | 30 | Quick rundown of features: 31 | 32 | - simple slopes analysis 33 | - calculation of Johnson-Neyman intervals 34 | - visualization of predicted and observed values using `ggplot2` 35 | 36 | All of these are implemented in a consistent interface designed to be as 37 | simple as possible with tweaks and guts available to advanced users. 38 | GLMs, models from the `survey` package, and multilevel models from 39 | `lme4` are fully supported as is visualization for Bayesian models from 40 | `rstanaram` and `brms`. Several other model types work “out of the box” 41 | even though they are not officially supported. 42 | 43 | ## Installation 44 | 45 | The package is available via CRAN. 46 | 47 | ``` r 48 | install.packages("interactions") 49 | ``` 50 | 51 | ## Usage 52 | 53 | Unless you have a keen eye and good familiarity with both the underlying 54 | mathematics and the scale of your variables, it can be very difficult to 55 | look at the output of regression model that includes an interaction and 56 | completely understand what the model is telling you. 57 | 58 | This package contains several means of aiding the understanding of and 59 | doing statistical inference with interactions. 60 | 61 | ### Johnson-Neyman intervals and simple slopes analysis 62 | 63 | The “classic” way of probing an interaction effect is to calculate the 64 | slope of the focal predictor at different values of the moderator. When 65 | the moderator is categorical, this is especially informative—e.g., what 66 | is the slope for cats vs. dogs? But you can also arbitrarily choose 67 | points for continuous moderators. 68 | 69 | With that said, the more statistically rigorous way to explore these 70 | effects is to find the Johnson-Neyman interval, which tells you the 71 | range of values of the moderator in which the slope of the predictor is 72 | significant vs.  nonsignificant at a specified alpha level. 73 | 74 | The `sim_slopes` function will by default find the Johnson-Neyman 75 | interval and tell you the predictor’s slope at specified values of the 76 | moderator; by default either both values of binary predictors or the 77 | mean and the mean +/- one standard deviation for continuous moderators. 78 | 79 | ``` r 80 | library(interactions) 81 | fiti <- lm(mpg ~ hp * wt, data = mtcars) 82 | sim_slopes(fiti, pred = hp, modx = wt, jnplot = TRUE) 83 | ``` 84 | 85 | #> JOHNSON-NEYMAN INTERVAL 86 | #> 87 | #> When wt is OUTSIDE the interval [3.69, 5.90], the slope of hp is p < .05. 88 | #> 89 | #> Note: The range of observed values of wt is [1.51, 5.42] 90 | 91 | ![](man/figures/j-n-plot-1.png) 92 | 93 | #> SIMPLE SLOPES ANALYSIS 94 | #> 95 | #> Slope of hp when wt = 2.238793 (- 1 SD): 96 | #> 97 | #> Est. S.E. t val. p 98 | #> ------- ------ -------- ------ 99 | #> -0.06 0.01 -5.66 0.00 100 | #> 101 | #> Slope of hp when wt = 3.217250 (Mean): 102 | #> 103 | #> Est. S.E. t val. p 104 | #> ------- ------ -------- ------ 105 | #> -0.03 0.01 -4.07 0.00 106 | #> 107 | #> Slope of hp when wt = 4.195707 (+ 1 SD): 108 | #> 109 | #> Est. S.E. t val. p 110 | #> ------- ------ -------- ------ 111 | #> -0.00 0.01 -0.31 0.76 112 | 113 | The Johnson-Neyman plot can help you get a handle on what the interval 114 | is telling you, too. Note that you can look at the Johnson-Neyman 115 | interval directly with the `johnson_neyman()` function. 116 | 117 | The above all generalize to three-way interactions, too, although 118 | Johnson-Neyman intervals do not handle the second moderator in the way 119 | that they do the first. 120 | 121 | ### Visualizing interaction effects 122 | 123 | This function plots two- and three-way interactions using `ggplot2` with 124 | a similar interface to the aforementioned `sim_slopes` function. Users 125 | can customize the appearance with familiar `ggplot2` commands. It 126 | supports several customizations, like confidence intervals. 127 | 128 | ``` r 129 | interact_plot(fiti, pred = hp, modx = wt, interval = TRUE) 130 | ``` 131 | 132 | ![](man/figures/interact_plot_continuous-1.png) 133 | 134 | You can also plot the observed data for comparison: 135 | 136 | ``` r 137 | interact_plot(fiti, pred = hp, modx = wt, plot.points = TRUE) 138 | ``` 139 | 140 | ![](man/figures/interact_plot_continuous_points-1.png) 141 | 142 | The function also supports categorical moderators—plotting observed data 143 | in these cases can reveal striking patterns. 144 | 145 | ``` r 146 | fitiris <- lm(Petal.Length ~ Petal.Width * Species, data = iris) 147 | interact_plot(fitiris, pred = Petal.Width, modx = Species, plot.points = TRUE) 148 | ``` 149 | 150 | ![](man/figures/interact_plot_factor-1.png) 151 | 152 | You may also combine the plotting and simple slopes functions by using 153 | `probe_interaction`, which calls both functions simultaneously. 154 | Categorical by categorical interactions can be investigated using the 155 | `cat_plot()` function. 156 | 157 | ## Contributing 158 | 159 | I’m happy to receive bug reports, suggestions, questions, and (most of 160 | all) contributions to fix problems and add features. I prefer you use 161 | the Github issues system over trying to reach out to me in other ways. 162 | Pull requests for contributions are encouraged. 163 | 164 | Please note that this project is released with a [Contributor Code of 165 | Conduct](https://github.com/jacob-long/interactions/blob/master/CONDUCT.md). 166 | By participating in this project you agree to abide by its terms. 167 | 168 | ## License 169 | 170 | The source code of this package is licensed under the [MIT 171 | License](https://opensource.org/licenses/mit-license.php). 172 | -------------------------------------------------------------------------------- /_pkgdown.yml: -------------------------------------------------------------------------------- 1 | url: https://interactions.jacob-long.com 2 | 3 | template: 4 | bootstrap: 5 5 | params: 6 | bootswatch: litera 7 | bslib: 8 | primary: "#570008" 9 | link-color: "#73000a" 10 | base_font: {google: "Lato"} 11 | font-family-sans-serif: font_google("Lato") 12 | font-family-serif: font_google("Lato") 13 | heading_font: {google: "Raleway"} 14 | code_font: {google: "IBM Plex Mono"} 15 | 16 | navbar: 17 | type: inverse 18 | left: 19 | - icon: fa-home 20 | href: index.html 21 | - icon: fa-newspaper-o 22 | text: "News" 23 | href: news/index.html 24 | - icon: fa-file-code-o 25 | text: "Documentation" 26 | href: reference/index.html 27 | - icon: fa-book 28 | text: "Vignettes" 29 | menu: 30 | - text: Exploring interactions with continuous predictors in regression models 31 | href: articles/interactions.html 32 | - text: Plotting interactions among categorical variables in regression models 33 | href: articles/categorical.html 34 | 35 | right: 36 | - icon: fa-github fa-lg 37 | text: "Github" 38 | href: https://github.com/jacob-long/interactions 39 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # DO NOT CHANGE the "init" and "install" sections below 2 | 3 | # Download script file from GitHub 4 | init: 5 | ps: | 6 | $ErrorActionPreference = "Stop" 7 | Invoke-WebRequest http://raw.github.com/krlmlr/r-appveyor/master/scripts/appveyor-tool.ps1 -OutFile "..\appveyor-tool.ps1" 8 | Import-Module '..\appveyor-tool.ps1' 9 | 10 | install: 11 | ps: Bootstrap 12 | 13 | cache: 14 | - C:\RLibrary 15 | 16 | # Adapt as necessary starting from here 17 | 18 | build_script: 19 | - travis-tool.sh install_deps 20 | 21 | test_script: 22 | - travis-tool.sh run_tests 23 | 24 | on_failure: 25 | - 7z a failure.zip *.Rcheck\* 26 | - appveyor PushArtifact failure.zip 27 | 28 | artifacts: 29 | - path: '*.Rcheck\**\*.log' 30 | name: Logs 31 | 32 | - path: '*.Rcheck\**\*.out' 33 | name: Logs 34 | 35 | - path: '*.Rcheck\**\*.fail' 36 | name: Logs 37 | 38 | - path: '*.Rcheck\**\*.Rout' 39 | name: Logs 40 | 41 | - path: '\*_*.tar.gz' 42 | name: Bits 43 | 44 | - path: '\*_*.zip' 45 | name: Bits 46 | -------------------------------------------------------------------------------- /cran-comments.md: -------------------------------------------------------------------------------- 1 | ## Test environments 2 | * Local macOS install, R 4.3.2 3 | * ubuntu-latest (via Github Actions), R-release, R-devel, oldrel-1 4 | * macOS-latest (via Github Actions), R-release 5 | * windows-latest (via Github Actions), R-release 6 | * Windows 2012 Server (on Appveyor) R-release, R-devel 7 | 8 | ## R CMD check results 9 | There were no NOTEs, WARNINGs, or ERRORs. 10 | -------------------------------------------------------------------------------- /hex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacob-long/interactions/df22b3ddcf5f88c7608f0cd08ef25e60bcdf4b99/hex.png -------------------------------------------------------------------------------- /inst/CITATION: -------------------------------------------------------------------------------- 1 | bibentry( 2 | bibtype = "Manual", 3 | title = "interactions: Comprehensive, User-Friendly Toolkit for Probing Interactions", 4 | author = person(c("Jacob","A."), "Long", email = "jacob.long@sc.edu", 5 | role = c("aut", "cre")), 6 | year = "2024", 7 | note = "R package version 1.2.0", 8 | url = "https://cran.r-project.org/package=interactions", 9 | key = "interactions", 10 | doi = "10.32614/CRAN.package.interactions" 11 | ) 12 | -------------------------------------------------------------------------------- /make_hex.R: -------------------------------------------------------------------------------- 1 | library(showtext) 2 | library(jtools) 3 | library(interactions) 4 | library(ggplot2) 5 | library(hexSticker) 6 | 7 | font_add("Fantasque Sans Mono", "FantasqueSansMono-Regular.ttf") 8 | font_add("monofur", "monof55.ttf") 9 | 10 | # states <- as.data.frame(state.x77) 11 | # states$HSGrad <- states$`HS Grad` 12 | # fit <- lm(Income ~ HSGrad + Murder, 13 | # data = states) 14 | # p <- effect_plot(model = fit, pred = Murder, x.label = "x", y.label = "y") 15 | 16 | set.seed(1000) 17 | x <- rnorm(1000) 18 | z <- rnorm(1000) 19 | y <- x + z + x*z + rnorm(1000, sd=.1) 20 | d <- data.frame(cbind(x, y, z)) 21 | p <- interact_plot(fit <- lm(y ~ x * z, data = d), x, z, colors = "CUD Bright") 22 | p <- p + theme(axis.text.y = element_blank(), axis.text.x = element_blank(), 23 | axis.title.x = element_text(color = "white", size = 5), axis.title.y = element_text(color = "white", size = 5), 24 | panel.grid.major = element_line(linetype='longdash', size = 0.25, color = "#e8e8e8b3"), legend.position = "none") + xlim(-1, 3) + 25 | ylim(-1.75, 5.75) 26 | 27 | plot(sticker(p + theme_transparent(), package="interactions", p_family = "Fantasque Sans Mono", 28 | s_width = 1.25, s_height = 1.2, s_x = 0.93, s_y = 0.83, p_size = 4.5, p_y = 1.53, h_color = "#570008", 29 | h_fill = "#844247", url = "interactions.jacob-long.com", u_size = 1.6, 30 | u_family = "Fantasque Sans Mono", filename = "hex.png", dpi = 500)) 31 | 32 | 33 | # atlantic: #466A9F 34 | # garnet: #73000a 35 | # sandstorm: #FFF2E3 36 | # azalea: #844247 37 | # dark garnet: #570008 38 | -------------------------------------------------------------------------------- /man/as_huxtable.sim_margins.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/simple_margins.R 3 | \name{as_huxtable.sim_margins} 4 | \alias{as_huxtable.sim_margins} 5 | \title{Create tabular output for simple margins analysis} 6 | \usage{ 7 | as_huxtable.sim_margins( 8 | x, 9 | format = "{estimate} ({std.error})", 10 | sig.levels = c(`***` = 0.001, `**` = 0.01, `*` = 0.05, `#` = 0.1), 11 | digits = getOption("jtools-digits", 2), 12 | conf.level = 0.95, 13 | intercept = attr(x, "cond.int"), 14 | int.format = format, 15 | ... 16 | ) 17 | } 18 | \arguments{ 19 | \item{x}{A \code{sim_margins} object.} 20 | 21 | \item{format}{The method for sharing the slope and associated uncertainty. 22 | Default is \code{"{estimate} ({std.error})"}. See the instructions for the 23 | \code{error_format} argument of \code{\link[jtools:export_summs]{jtools::export_summs()}} for more on your 24 | options.} 25 | 26 | \item{sig.levels}{A named vector in which the values are potential p value 27 | thresholds and the names are significance markers (e.g., "*") for when 28 | p values are below the threshold. Default is 29 | \code{c(`***` = .001, `**` = .01, `*` = .05, `#` = .1)}.} 30 | 31 | \item{digits}{How many digits should the outputted table round to? Default 32 | is 2.} 33 | 34 | \item{conf.level}{How wide the confidence interval should be, if it 35 | is used. .95 (95\% interval) is the default.} 36 | 37 | \item{intercept}{Should conditional intercepts be included? Default is 38 | whatever the \code{cond.int} argument to \code{x} was.} 39 | 40 | \item{int.format}{If conditional intercepts were requested, how should 41 | they be formatted? Default is the same as \code{format}.} 42 | 43 | \item{...}{Ignored.} 44 | } 45 | \description{ 46 | This function converts a \code{sim_margins} object into a 47 | \code{huxtable} object, making it suitable for use in external documents. 48 | } 49 | \details{ 50 | For more on what you can do with a \code{huxtable}, see \pkg{huxtable}. 51 | } 52 | -------------------------------------------------------------------------------- /man/as_huxtable.sim_slopes.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/simple_slopes.R 3 | \name{as_huxtable.sim_slopes} 4 | \alias{as_huxtable.sim_slopes} 5 | \title{Create tabular output for simple slopes analysis} 6 | \usage{ 7 | as_huxtable.sim_slopes( 8 | x, 9 | format = "{estimate} ({std.error})", 10 | sig.levels = c(`***` = 0.001, `**` = 0.01, `*` = 0.05, `#` = 0.1), 11 | digits = getOption("jtools-digits", 2), 12 | conf.level = 0.95, 13 | intercept = attr(x, "cond.int"), 14 | int.format = format, 15 | ... 16 | ) 17 | } 18 | \arguments{ 19 | \item{x}{The \code{\link[=sim_slopes]{sim_slopes()}} object.} 20 | 21 | \item{format}{The method for sharing the slope and associated uncertainty. 22 | Default is \code{"{estimate} ({std.error})"}. See the instructions for the 23 | \code{error_format} argument of \code{\link[jtools:export_summs]{jtools::export_summs()}} for more on your 24 | options.} 25 | 26 | \item{sig.levels}{A named vector in which the values are potential p value 27 | thresholds and the names are significance markers (e.g., "*") for when 28 | p values are below the threshold. Default is 29 | \code{c(`***` = .001, `**` = .01, `*` = .05, `#` = .1)}.} 30 | 31 | \item{digits}{How many digits should the outputted table round to? Default 32 | is 2.} 33 | 34 | \item{conf.level}{How wide the confidence interval should be, if it 35 | is used. .95 (95\% interval) is the default.} 36 | 37 | \item{intercept}{Should conditional intercepts be included? Default is 38 | whatever the \code{cond.int} argument to \code{x} was.} 39 | 40 | \item{int.format}{If conditional intercepts were requested, how should 41 | they be formatted? Default is the same as \code{format}.} 42 | 43 | \item{...}{Ignored.} 44 | } 45 | \description{ 46 | This function converts a \code{sim_slopes} object into a 47 | \code{huxtable} object, making it suitable for use in external documents. 48 | } 49 | \details{ 50 | For more on what you can do with a \code{huxtable}, see \pkg{huxtable}. 51 | } 52 | -------------------------------------------------------------------------------- /man/figures/interact_plot_continuous-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacob-long/interactions/df22b3ddcf5f88c7608f0cd08ef25e60bcdf4b99/man/figures/interact_plot_continuous-1.png -------------------------------------------------------------------------------- /man/figures/interact_plot_continuous_points-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacob-long/interactions/df22b3ddcf5f88c7608f0cd08ef25e60bcdf4b99/man/figures/interact_plot_continuous_points-1.png -------------------------------------------------------------------------------- /man/figures/interact_plot_factor-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacob-long/interactions/df22b3ddcf5f88c7608f0cd08ef25e60bcdf4b99/man/figures/interact_plot_factor-1.png -------------------------------------------------------------------------------- /man/figures/j-n-plot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacob-long/interactions/df22b3ddcf5f88c7608f0cd08ef25e60bcdf4b99/man/figures/j-n-plot-1.png -------------------------------------------------------------------------------- /man/figures/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacob-long/interactions/df22b3ddcf5f88c7608f0cd08ef25e60bcdf4b99/man/figures/logo.png -------------------------------------------------------------------------------- /man/johnson_neyman.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/johnson_neyman.R 3 | \name{johnson_neyman} 4 | \alias{johnson_neyman} 5 | \title{Calculate Johnson-Neyman intervals for 2-way interactions} 6 | \usage{ 7 | johnson_neyman( 8 | model, 9 | pred, 10 | modx, 11 | vmat = NULL, 12 | alpha = 0.05, 13 | plot = TRUE, 14 | control.fdr = FALSE, 15 | line.thickness = 0.5, 16 | df = "residual", 17 | digits = getOption("jtools-digits", 2), 18 | critical.t = NULL, 19 | sig.color = "#00BFC4", 20 | insig.color = "#F8766D", 21 | mod.range = NULL, 22 | title = "Johnson-Neyman plot", 23 | y.label = NULL, 24 | modx.label = NULL 25 | ) 26 | } 27 | \arguments{ 28 | \item{model}{A regression model. It is tested with \code{lm}, \code{glm}, and 29 | \code{svyglm} objects, but others may work as well. It should contain the 30 | interaction of interest. Be aware that just because the computations 31 | work, this does not necessarily mean the procedure is appropriate for 32 | the type of model you have.} 33 | 34 | \item{pred}{The predictor variable involved in the interaction.} 35 | 36 | \item{modx}{The moderator variable involved in the interaction.} 37 | 38 | \item{vmat}{Optional. You may supply the variance-covariance matrix of the 39 | coefficients yourself. This is useful if you are using robust standard 40 | errors, as you could if using the \pkg{sandwich} package.} 41 | 42 | \item{alpha}{The alpha level. By default, the standard 0.05.} 43 | 44 | \item{plot}{Should a plot of the results be printed? Default is \code{TRUE}. 45 | The \code{ggplot2} object is returned either way.} 46 | 47 | \item{control.fdr}{Logical. Use the procedure described in Esarey & Sumner 48 | (2017) to limit the false discovery rate? Default is FALSE. See details 49 | for more on this method.} 50 | 51 | \item{line.thickness}{How thick should the predicted line be? This is 52 | passed to \code{geom_path} as the \code{size} argument, but because of the way the 53 | line is created, you cannot use \code{geom_path} to modify the output plot 54 | yourself.} 55 | 56 | \item{df}{How should the degrees of freedom be calculated for the critical 57 | test statistic? Previous versions used the large sample approximation; if 58 | alpha was .05, the critical test statistic was 1.96 regardless of sample 59 | size and model complexity. The default is now "residual", meaning the same 60 | degrees of freedom used to calculate p values for regression coefficients. 61 | You may instead choose any number or "normal", which reverts to the 62 | previous behavior. The argument is not used if \code{control.fdr = TRUE}.} 63 | 64 | \item{digits}{An integer specifying the number of digits past the decimal to 65 | report in the output. Default is 2. You can change the default number of 66 | digits for all jtools functions with 67 | \code{options("jtools-digits" = digits)} where digits is the desired 68 | number.} 69 | 70 | \item{critical.t}{If you want to provide the critical test statistic instead 71 | relying on a normal or \emph{t} approximation, or the \code{control.fdr} calculation, 72 | you can give that value here. This allows you to use other methods for 73 | calculating it.} 74 | 75 | \item{sig.color}{Sets the color for areas of the Johnson-Neyman plot where 76 | the slope of the moderator is significant at the specified level. \code{"black"} 77 | can be a good choice for greyscale publishing.} 78 | 79 | \item{insig.color}{Sets the color for areas of the Johnson-Neyman plot where 80 | the slope of the moderator is insignificant at the specified level. \code{"grey"} 81 | can be a good choice for greyscale publishing.} 82 | 83 | \item{mod.range}{The range of values of the moderator (the x-axis) to plot. 84 | By default, this goes from one standard deviation below the observed range 85 | to one standard deviation above the observed range and the observed range 86 | is highlighted on the plot. You could instead choose to provide the 87 | actual observed minimum and maximum, in which case the range of the 88 | observed data is not highlighted in the plot. Provide the range as a vector, 89 | e.g., \code{c(0, 10)}.} 90 | 91 | \item{title}{The plot title. \code{"Johnson-Neyman plot"} by default.} 92 | 93 | \item{y.label}{If you prefer to override the automatic labelling of the 94 | y axis, you can specify your own label here. The y axis represents a 95 | \emph{slope} so it is recommended that you do not simply give the name of the 96 | predictor variable but instead make clear that it is a slope. By default, 97 | "Slope of [pred]" is used (with whatever \code{pred} is).} 98 | 99 | \item{modx.label}{If you prefer to override the automatic labelling of 100 | the x axis, you can specify your own label here. By default, the name 101 | \code{modx} is used.} 102 | } 103 | \value{ 104 | \item{bounds}{The two numbers that make up the interval.} 105 | \item{cbands}{A dataframe with predicted values of the predictor's slope 106 | and lower/upper bounds of confidence bands if you would like to make your 107 | own plots} 108 | \item{plot}{The \code{ggplot} object used for plotting. You can tweak the 109 | plot like you could any other from \code{ggplot}.} 110 | } 111 | \description{ 112 | \code{johnson_neyman} finds so-called "Johnson-Neyman" intervals for 113 | understanding where simple slopes are significant in the context of 114 | interactions in multiple linear regression. 115 | } 116 | \details{ 117 | The interpretation of the values given by this function is important and not 118 | always immediately intuitive. For an interaction between a predictor variable 119 | and moderator variable, it is often the case that the slope of the predictor 120 | is statistically significant at only some values of the moderator. For 121 | example, perhaps the effect of your predictor is only significant when the 122 | moderator is set at some high value. 123 | 124 | The Johnson-Neyman interval provides the two values of the moderator at 125 | which the slope of the predictor goes from non-significant to significant. 126 | Usually, the predictor's slope is only significant \emph{outside} of the 127 | range given by the function. The output of this function will make it clear 128 | either way. 129 | 130 | One weakness of this method of probing interactions is that it is analogous 131 | to making multiple comparisons without any adjustment to the alpha level. 132 | Esarey & Sumner (2017) proposed a method for addressing this, which is 133 | implemented in the \code{interactionTest} package. This function implements that 134 | procedure with modifications to the \code{interactionTest} code (that package is 135 | not required to use this function). If you set \code{control.fdr = TRUE}, an 136 | alternative \emph{t} statistic will be calculated based on your specified alpha 137 | level and the data. This will always be a more conservative test than when 138 | \code{control.fdr = FALSE}. The printed output will report the calculated 139 | critical \emph{t} statistic. 140 | 141 | This technique is not easily ported to 3-way interaction contexts. You could, 142 | however, look at the J-N interval at two different levels of a second 143 | moderator. This does forgo a benefit of the J-N technique, which is not 144 | having to pick arbitrary points. If you want to do this, just use the 145 | \code{\link{sim_slopes}} function's ability to handle 3-way interactions 146 | and request Johnson-Neyman intervals for each. 147 | } 148 | \examples{ 149 | # Using a fitted lm model 150 | states <- as.data.frame(state.x77) 151 | states$HSGrad <- states$`HS Grad` 152 | fit <- lm(Income ~ HSGrad + Murder*Illiteracy, 153 | data = states) 154 | johnson_neyman(model = fit, pred = Murder, 155 | modx = Illiteracy) 156 | 157 | } 158 | \references{ 159 | Bauer, D. J., & Curran, P. J. (2005). Probing interactions in fixed and 160 | multilevel regression: Inferential and graphical techniques. 161 | \emph{Multivariate Behavioral Research}, \emph{40}(3), 373-400. 162 | \doi{10.1207/s15327906mbr4003_5} 163 | 164 | Esarey, J., & Sumner, J. L. (2017). Marginal effects in interaction models: 165 | Determining and controlling the false positive rate. Comparative Political 166 | Studies, 1–33. Advance online publication. 167 | \doi{10.1177/0010414017730080} 168 | 169 | Johnson, P.O. & Fay, L.C. (1950). The Johnson-Neyman technique, its theory 170 | and application. \emph{Psychometrika}, \emph{15}, 349-367. 171 | \doi{10.1007/BF02288864} 172 | } 173 | \seealso{ 174 | Other interaction tools: 175 | \code{\link{probe_interaction}()}, 176 | \code{\link{sim_margins}()}, 177 | \code{\link{sim_slopes}()} 178 | } 179 | \author{ 180 | Jacob Long \email{jacob.long@sc.edu} 181 | } 182 | \concept{interaction tools} 183 | -------------------------------------------------------------------------------- /man/plot.sim_margins.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/simple_margins.R 3 | \name{plot.sim_margins} 4 | \alias{plot.sim_margins} 5 | \title{Plot coefficients from simple slopes analysis} 6 | \usage{ 7 | \method{plot}{sim_margins}(x, ...) 8 | } 9 | \arguments{ 10 | \item{x}{A \code{\link[=sim_margins]{sim_margins()}} object.} 11 | 12 | \item{...}{arguments passed to \code{\link[jtools:plot_summs]{jtools::plot_coefs()}}} 13 | } 14 | \description{ 15 | This creates a coefficient plot to visually summarize the 16 | results of simple slopes analysis. 17 | } 18 | -------------------------------------------------------------------------------- /man/plot.sim_slopes.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/simple_slopes.R 3 | \name{plot.sim_slopes} 4 | \alias{plot.sim_slopes} 5 | \title{Plot coefficients from simple slopes analysis} 6 | \usage{ 7 | \method{plot}{sim_slopes}(x, ...) 8 | } 9 | \arguments{ 10 | \item{x}{A \code{\link[=sim_slopes]{sim_slopes()}} object.} 11 | 12 | \item{...}{arguments passed to \code{\link[jtools:plot_summs]{jtools::plot_coefs()}}} 13 | } 14 | \description{ 15 | This creates a coefficient plot to visually summarize the 16 | results of simple slopes analysis. 17 | } 18 | -------------------------------------------------------------------------------- /man/probe_interaction.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/int_utils.R 3 | \name{probe_interaction} 4 | \alias{probe_interaction} 5 | \title{Probe interaction effects via simple slopes and plotting} 6 | \usage{ 7 | probe_interaction(model, pred, modx, mod2 = NULL, ...) 8 | } 9 | \arguments{ 10 | \item{model}{A regression model. The function is tested with \code{lm}, 11 | \code{glm}, \code{\link[survey]{svyglm}}, \code{\link[lme4]{merMod}}, 12 | \code{\link[quantreg]{rq}}, \code{\link[brms]{brmsfit}}, 13 | \code{stanreg} models. 14 | Models from other classes may work as well but are not officially 15 | supported. The model should include the interaction of interest.} 16 | 17 | \item{pred}{The name of the predictor variable involved 18 | in the interaction. This can be a bare name or string. Note that it 19 | is evaluated using \code{rlang}, so programmers can use the \verb{!!} syntax 20 | to pass variables instead of the verbatim names.} 21 | 22 | \item{modx}{The name of the moderator variable involved 23 | in the interaction. This can be a bare name or string. The same 24 | \code{rlang} proviso applies as with \code{pred}.} 25 | 26 | \item{mod2}{Optional. The name of the second moderator 27 | variable involved in the interaction. This can be a bare name or string. 28 | The same \code{rlang} proviso applies as with \code{pred}.} 29 | 30 | \item{...}{Other arguments accepted by \code{\link{sim_slopes}} and 31 | \code{\link{interact_plot}}} 32 | } 33 | \value{ 34 | \item{simslopes}{The \code{sim_slopes} object created.} 35 | \item{interactplot}{The \code{ggplot} object created by 36 | \code{interact_plot}.} 37 | } 38 | \description{ 39 | \code{probe_interaction} is a convenience function that allows users to call 40 | both \code{\link{sim_slopes}} and \code{\link{interact_plot}} with a single 41 | call. 42 | } 43 | \details{ 44 | This function simply merges the nearly-equivalent arguments needed to call 45 | both \code{\link{sim_slopes}} and \code{\link{interact_plot}} without the 46 | need for re-typing their common arguments. Note that each function is called 47 | separately and they re-fit a separate model for each level of each 48 | moderator; therefore, the runtime may be considerably longer than the 49 | original model fit. For larger models, this is worth keeping in mind. 50 | 51 | Sometimes, you may want different parameters when doing simple slopes 52 | analysis compared to when plotting interaction effects. For instance, it is 53 | often easier to interpret the regression output when variables are 54 | standardized; but plots are often easier to understand when the variables 55 | are in their original units of measure. 56 | 57 | \code{probe_interaction} does not 58 | support providing different arguments to each function. If that is needed, 59 | use \code{sim_slopes} and \code{interact_plot} directly. 60 | } 61 | \examples{ 62 | 63 | # Using a fitted model as formula input 64 | fiti <- lm(Income ~ Frost + Murder * Illiteracy, 65 | data=as.data.frame(state.x77)) 66 | probe_interaction(model = fiti, pred = Murder, modx = Illiteracy, 67 | modx.values = "plus-minus") 68 | # 3-way interaction 69 | fiti3 <- lm(Income ~ Frost * Murder * Illiteracy, 70 | data=as.data.frame(state.x77)) 71 | probe_interaction(model = fiti3, pred = Murder, modx = Illiteracy, 72 | mod2 = Frost, mod2.values = "plus-minus") 73 | 74 | # With svyglm 75 | if (requireNamespace("survey")) { 76 | library(survey) 77 | data(api) 78 | dstrat <- svydesign(id = ~1, strata = ~stype, weights = ~pw, 79 | data = apistrat, fpc = ~fpc) 80 | regmodel <- svyglm(api00 ~ ell * meals + sch.wide, design = dstrat) 81 | probe_interaction(model = regmodel, pred = ell, modx = meals, 82 | modx.values = "plus-minus", cond.int = TRUE) 83 | 84 | # 3-way with survey and factor input 85 | regmodel3 <- svyglm(api00 ~ ell * meals * sch.wide, design = dstrat) 86 | probe_interaction(model = regmodel3, pred = ell, modx = meals, 87 | mod2 = sch.wide) 88 | # Can try different configurations of 1st vs 2nd mod 89 | probe_interaction(model = regmodel3, pred = ell, modx = sch.wide, 90 | mod2 = meals) 91 | } 92 | 93 | } 94 | \seealso{ 95 | Other interaction tools: 96 | \code{\link{johnson_neyman}()}, 97 | \code{\link{sim_margins}()}, 98 | \code{\link{sim_slopes}()} 99 | } 100 | \author{ 101 | Jacob Long \email{jacob.long@sc.edu} 102 | } 103 | \concept{interaction tools} 104 | -------------------------------------------------------------------------------- /man/reexports.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \docType{import} 4 | \name{reexports} 5 | \alias{reexports} 6 | \alias{tidy} 7 | \alias{glance} 8 | \title{Objects exported from other packages} 9 | \keyword{internal} 10 | \description{ 11 | These objects are imported from other packages. Follow the links 12 | below to see their documentation. 13 | 14 | \describe{ 15 | \item{generics}{\code{\link[generics]{glance}}, \code{\link[generics]{tidy}}} 16 | }} 17 | 18 | -------------------------------------------------------------------------------- /man/sim_margins.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/simple_margins.R 3 | \name{sim_margins} 4 | \alias{sim_margins} 5 | \title{Perform a simple margins analysis.} 6 | \usage{ 7 | sim_margins( 8 | model, 9 | pred, 10 | modx, 11 | mod2 = NULL, 12 | modx.values = NULL, 13 | mod2.values = NULL, 14 | data = NULL, 15 | cond.int = FALSE, 16 | vce = c("delta", "simulation", "bootstrap", "none"), 17 | iterations = 1000, 18 | digits = getOption("jtools-digits", default = 2), 19 | pvals = TRUE, 20 | confint = FALSE, 21 | ci.width = 0.95, 22 | cluster = NULL, 23 | modx.labels = NULL, 24 | mod2.labels = NULL, 25 | ... 26 | ) 27 | } 28 | \arguments{ 29 | \item{model}{A regression model. The function is tested with \code{lm}, 30 | \code{glm}, \code{\link[survey]{svyglm}}, \code{\link[lme4]{merMod}}, 31 | \code{\link[quantreg]{rq}}, \code{\link[brms]{brmsfit}}, 32 | \code{stanreg} models. 33 | Models from other classes may work as well but are not officially 34 | supported. The model should include the interaction of interest.} 35 | 36 | \item{pred}{The name of the predictor variable involved 37 | in the interaction. This can be a bare name or string. Note that it 38 | is evaluated using \code{rlang}, so programmers can use the \verb{!!} syntax 39 | to pass variables instead of the verbatim names.} 40 | 41 | \item{modx}{The name of the moderator variable involved 42 | in the interaction. This can be a bare name or string. The same 43 | \code{rlang} proviso applies as with \code{pred}.} 44 | 45 | \item{mod2}{Optional. The name of the second moderator 46 | variable involved in the interaction. This can be a bare name or string. 47 | The same \code{rlang} proviso applies as with \code{pred}.} 48 | 49 | \item{modx.values}{For which values of the moderator should lines be 50 | plotted? There are two basic options: 51 | \itemize{ 52 | \item A vector of values (e.g., \code{c(1, 2, 3)}) 53 | \item A single argument asking to calculate a set of values. See details 54 | below. 55 | } 56 | 57 | Default is \code{NULL}. If \code{NULL} (or \code{mean-plus-minus}), 58 | then the customary +/- 1 standard 59 | deviation from the mean as well as the mean itself are used for continuous 60 | moderators. If \code{"plus-minus"}, plots lines when the moderator is at 61 | +/- 1 standard deviation without the mean. You may also choose \code{"terciles"} 62 | to split the data into equally-sized groups and choose the point at the 63 | mean of each of those groups. 64 | 65 | If the moderator is a factor variable and \code{modx.values} is 66 | \code{NULL}, each level of the factor is included. You may specify 67 | any subset of the factor levels (e.g., \code{c("Level 1", "Level 3")}) as long 68 | as there is more than 1. The levels will be plotted in the order you 69 | provide them, so this can be used to reorder levels as well.} 70 | 71 | \item{mod2.values}{For which values of the second moderator should the plot 72 | be 73 | facetted by? That is, there will be a separate plot for each level of this 74 | moderator. Defaults are the same as \code{modx.values}.} 75 | 76 | \item{data}{Optional, default is NULL. You may provide the data used to 77 | fit the model. This can be a better way to get mean values for centering 78 | and can be crucial for models with variable transformations in the formula 79 | (e.g., \code{log(x)}) or polynomial terms (e.g., \code{poly(x, 2)}). You will 80 | see a warning if the function detects problems that would likely be 81 | solved by providing the data with this argument and the function will 82 | attempt to retrieve the original data from the global environment.} 83 | 84 | \item{cond.int}{Should conditional intercepts be printed in addition to the 85 | slopes? Default is \code{FALSE}.} 86 | 87 | \item{vce}{A character string indicating the type of estimation procedure to use for estimating variances. The default (\dQuote{delta}) uses the delta method. Alternatives are \dQuote{bootstrap}, which uses bootstrap estimation, or \dQuote{simulation}, which averages across simulations drawn from the joint sampling distribution of model coefficients. The latter two are extremely time intensive.} 88 | 89 | \item{iterations}{If \code{vce = "bootstrap"}, the number of bootstrap iterations. If \code{vce = "simulation"}, the number of simulated effects to draw. Ignored otherwise.} 90 | 91 | \item{digits}{An integer specifying the number of digits past the decimal to 92 | report in the output. Default is 2. You can change the default number of 93 | digits for all jtools functions with 94 | \code{options("jtools-digits" = digits)} where digits is the desired 95 | number.} 96 | 97 | \item{pvals}{Show p values? If \code{FALSE}, these 98 | are not printed. Default is \code{TRUE}.} 99 | 100 | \item{confint}{Show confidence intervals instead of standard errors? Default 101 | is \code{FALSE}.} 102 | 103 | \item{ci.width}{A number between 0 and 1 that signifies the width of the 104 | desired confidence interval. Default is \code{.95}, which corresponds 105 | to a 95\% confidence interval. Ignored if \code{confint = FALSE}.} 106 | 107 | \item{cluster}{For clustered standard errors, provide the column name of 108 | the cluster variable in the input data frame (as a string). Alternately, 109 | provide a vector of clusters.} 110 | 111 | \item{modx.labels}{A character vector of labels for each level of the 112 | moderator values, provided in the same order as the \code{modx.values} 113 | argument. If \code{NULL}, the values themselves are used as labels unless 114 | \code{modx,values} is also \code{NULL}. In that case, "+1 SD" and "-1 SD" 115 | are used.} 116 | 117 | \item{mod2.labels}{A character vector of labels for each level of the 2nd 118 | moderator values, provided in the same order as the \code{mod2.values} 119 | argument. If \code{NULL}, the values themselves are used as labels unless 120 | \code{mod2.values} is also \code{NULL}. In that case, "+1 SD" and "-1 SD" 121 | are used.} 122 | 123 | \item{...}{ignored.} 124 | } 125 | \value{ 126 | A list object with the following components: 127 | 128 | \item{slopes}{A table of coefficients for the focal predictor at each 129 | value of the moderator} 130 | \item{ints}{A table of coefficients for the intercept at each value of the 131 | moderator} 132 | \item{modx.values}{The values of the moderator used in the analysis} 133 | } 134 | \description{ 135 | \code{sim_margins} conducts a simple margins analysis for the purposes of 136 | understanding two- and three-way interaction effects in linear regression. 137 | } 138 | \details{ 139 | This allows the user to perform a simple margins analysis for the 140 | purpose of probing interaction effects in a linear regression. Two- and 141 | three-way interactions are supported, though one should be warned that 142 | three-way interactions are not easy to interpret in this way. 143 | 144 | The function is tested with \code{lm}, \code{glm}, \code{svyglm}, and \code{merMod} inputs. 145 | Others may work as well, but are not tested. In all but the linear model 146 | case, be aware that not all the assumptions applied to simple slopes 147 | analysis apply. 148 | } 149 | \references{ 150 | Bauer, D. J., & Curran, P. J. (2005). Probing interactions in fixed and 151 | multilevel regression: Inferential and graphical techniques. 152 | \emph{Multivariate Behavioral Research}, \emph{40}(3), 373-400. 153 | \doi{10.1207/s15327906mbr4003_5} 154 | 155 | Cohen, J., Cohen, P., West, S. G., & Aiken, L. S. (2003). \emph{Applied 156 | multiple regression/correlation analyses for the behavioral sciences} (3rd 157 | ed.). Mahwah, NJ: Lawrence Erlbaum Associates, Inc. 158 | 159 | Hanmer, M. J., & Kalkan, K. O. (2013). Behind the curve: Clarifying the best 160 | approach to calculating predicted probabilities and marginal effects from 161 | limited dependent variable models. \emph{American Journal of Political Science}, 162 | \emph{57}, 263–277. \doi{10.1111/j.1540-5907.2012.00602.x} 163 | } 164 | \seealso{ 165 | \code{\link[margins:margins]{margins::margins()}} 166 | 167 | Other interaction tools: 168 | \code{\link{johnson_neyman}()}, 169 | \code{\link{probe_interaction}()}, 170 | \code{\link{sim_slopes}()} 171 | } 172 | \author{ 173 | Jacob Long \email{jacob.long@sc.edu} 174 | } 175 | \concept{interaction tools} 176 | -------------------------------------------------------------------------------- /man/sim_margins_tidiers.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/simple_margins.R 3 | \name{tidy.sim_margins} 4 | \alias{tidy.sim_margins} 5 | \alias{glance.sim_margins} 6 | \title{Tidiers for \code{\link[=sim_margins]{sim_margins()}} objects.} 7 | \usage{ 8 | \method{tidy}{sim_margins}(x, conf.level = 0.95, ...) 9 | 10 | \method{glance}{sim_margins}(x, ...) 11 | } 12 | \arguments{ 13 | \item{x}{The \code{sim_margins} object} 14 | 15 | \item{conf.level}{The width of confidence intervals. Default is .95 (95\%).} 16 | 17 | \item{...}{Ignored.} 18 | } 19 | \description{ 20 | You can use \code{\link[broom:reexports]{broom::tidy()}} and \code{\link[broom:reexports]{broom::glance()}} for "tidy" 21 | methods of storing \code{sim_margins} output. 22 | } 23 | -------------------------------------------------------------------------------- /man/sim_slopes_tidiers.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/simple_slopes.R 3 | \name{tidy.sim_slopes} 4 | \alias{tidy.sim_slopes} 5 | \alias{glance.sim_slopes} 6 | \title{Tidiers for \code{\link[=sim_slopes]{sim_slopes()}} objects.} 7 | \usage{ 8 | \method{tidy}{sim_slopes}(x, conf.level = 0.95, ...) 9 | 10 | \method{glance}{sim_slopes}(x, ...) 11 | } 12 | \arguments{ 13 | \item{x}{The \code{sim_slopes} object} 14 | 15 | \item{conf.level}{The width of confidence intervals. Default is .95 (95\%).} 16 | 17 | \item{...}{Ignored.} 18 | } 19 | \description{ 20 | You can use \code{\link[broom:reexports]{broom::tidy()}} and \code{\link[broom:reexports]{broom::glance()}} for "tidy" 21 | methods of storing \code{sim_slopes} output. 22 | } 23 | -------------------------------------------------------------------------------- /pkgdown/extra.css: -------------------------------------------------------------------------------- 1 | .page-header { 2 | min-height:25px !important; 3 | } 4 | 5 | .template-home img.logo { 6 | width: 200px; 7 | } 8 | 9 | .text-muted { 10 | color: #ced4da !important; 11 | } 12 | 13 | .bg-primary .navbar-nav .nav-link:hover, .bg-primary .navbar-nav .nav-link:hover { 14 | color: #ced4da !important; 15 | } 16 | 17 | .huxtable tr, .huxtable th, .huxtable td { 18 | border-style: none; 19 | } 20 | 21 | aside h2 { 22 | margin-top: 2rem; 23 | } 24 | 25 | .page-header { 26 | margin-top: 2rem; 27 | border-bottom: none; 28 | } 29 | 30 | h1, h2, h3, h4 { 31 | font-weight: 400; 32 | } 33 | -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacob-long/interactions/df22b3ddcf5f88c7608f0cd08ef25e60bcdf4b99/pkgdown/favicon/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacob-long/interactions/df22b3ddcf5f88c7608f0cd08ef25e60bcdf4b99/pkgdown/favicon/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacob-long/interactions/df22b3ddcf5f88c7608f0cd08ef25e60bcdf4b99/pkgdown/favicon/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacob-long/interactions/df22b3ddcf5f88c7608f0cd08ef25e60bcdf4b99/pkgdown/favicon/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacob-long/interactions/df22b3ddcf5f88c7608f0cd08ef25e60bcdf4b99/pkgdown/favicon/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacob-long/interactions/df22b3ddcf5f88c7608f0cd08ef25e60bcdf4b99/pkgdown/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacob-long/interactions/df22b3ddcf5f88c7608f0cd08ef25e60bcdf4b99/pkgdown/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacob-long/interactions/df22b3ddcf5f88c7608f0cd08ef25e60bcdf4b99/pkgdown/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacob-long/interactions/df22b3ddcf5f88c7608f0cd08ef25e60bcdf4b99/pkgdown/favicon/favicon.ico -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(vdiffr) 3 | library(interactions) 4 | 5 | test_check("interactions") 6 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/cat_plot/p0bar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 0 67 | 2500 68 | 5000 69 | 7500 70 | E 71 | F 72 | G 73 | H 74 | I 75 | J 76 | color 77 | price 78 | p0bar 79 | 80 | 81 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/cat_plot/p0bari.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 0 67 | 2500 68 | 5000 69 | 7500 70 | E 71 | F 72 | G 73 | H 74 | I 75 | J 76 | color 77 | price 78 | p0bari 79 | 80 | 81 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/cat_plot/p0pt.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 0 67 | 2500 68 | 5000 69 | 7500 70 | E 71 | F 72 | G 73 | H 74 | I 75 | J 76 | color 77 | price 78 | p0pt 79 | 80 | 81 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/cat_plot/p0pti.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 0 67 | 2500 68 | 5000 69 | 7500 70 | E 71 | F 72 | G 73 | H 74 | I 75 | J 76 | color 77 | price 78 | p0pti 79 | 80 | 81 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/cat_plot/pcatbfit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 4 47 | 5 48 | 6 49 | 7 50 | 0 51 | 1 52 | Trt 53 | count 54 | pcatbfit 55 | 56 | 57 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/cat_plot/pglmcatoff.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 0.475 47 | 0.500 48 | 0.525 49 | 0.550 50 | 0 51 | 1 52 | talent 53 | counts 54 | pglmcatoff 55 | 56 | 57 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/cat_plot/plme4cat.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 19.85 44 | 19.90 45 | 19.95 46 | 20.00 47 | 20.05 48 | want 49 | do 50 | mode 51 | Anger 52 | Gender 53 | 54 | 55 | 56 | 57 | F 58 | M 59 | plme4cat 60 | 61 | 62 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/cat_plot/psvycat.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 560 47 | 580 48 | 600 49 | 620 50 | No 51 | Yes 52 | both 53 | api00 54 | psvycat 55 | 56 | 57 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/interact_plot/pbfcat.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 4 47 | 5 48 | 6 49 | 7 50 | 0 51 | 1 52 | Trt 53 | count 54 | pbfcat 55 | 56 | 57 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/interact_plot/pglmoff.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 0.4 44 | 0.5 45 | 0.6 46 | 0.7 47 | 5 48 | 10 49 | 15 50 | 20 51 | talent 52 | counts 53 | money 54 | 55 | 56 | 57 | 58 | 59 | 60 | + 1 SD 61 | Mean 62 | - 1 SD 63 | pglmoff 64 | 65 | 66 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/interact_plot/pglmrob.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 0.4 44 | 0.5 45 | 0.6 46 | 0.7 47 | 5 48 | 10 49 | 15 50 | 20 51 | talent 52 | counts 53 | money 54 | 55 | 56 | 57 | 58 | 59 | 60 | + 1 SD 61 | Mean 62 | - 1 SD 63 | pglmrob 64 | 65 | 66 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/interact_plot/plme4-interact-plot-to-cat-plot.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 19.85 46 | 19.90 47 | 19.95 48 | 20.00 49 | 20.05 50 | want 51 | do 52 | mode 53 | Anger 54 | Gender 55 | 56 | 57 | 58 | 59 | 60 | 61 | F 62 | M 63 | plme4 interact_plot to cat_plot 64 | 65 | 66 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/interact_plot/plme4.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 19.85 42 | 19.90 43 | 19.95 44 | 20.00 45 | 20.05 46 | 1 47 | 2 48 | mode_numeric 49 | Anger 50 | Gender 51 | 52 | 53 | 54 | 55 | F 56 | M 57 | plme4 58 | 59 | 60 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/interact_plot/plmlabelscpred-interact-plot-to-cat-plot.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 4000 47 | 4500 48 | 5000 49 | 0 50 | 1 51 | o70 52 | Income 53 | HSGrad 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | + 1 SD 64 | Mean 65 | - 1 SD 66 | plmlabelscpred interact_plot to cat_plot 67 | 68 | 69 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/interact_plot/plmlabelscpred.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 4000 41 | 4500 42 | 5000 43 | Under 44 | Over 45 | o70n 46 | Income 47 | HSGrad 48 | 49 | 50 | 51 | 52 | 53 | 54 | + 1 SD 55 | Mean 56 | - 1 SD 57 | plmlabelscpred 58 | 59 | 60 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/interact_plot/plmnfchar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 3500 44 | 4000 45 | 4500 46 | 5000 47 | 5500 48 | 40 49 | 50 50 | 60 51 | HSGrad 52 | Income 53 | Murder 54 | 55 | 56 | 57 | 58 | 59 | 60 | + 1 SD 61 | Mean 62 | - 1 SD 63 | plmnfchar 64 | 65 | 66 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/interact_plot/plmtf.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 3500 43 | 4000 44 | 4500 45 | 5000 46 | 5500 47 | 40 48 | 50 49 | 60 50 | HSGrad 51 | Income 52 | o70l 53 | 54 | 55 | 56 | 57 | TRUE 58 | FALSE 59 | plmtf 60 | 61 | 62 | -------------------------------------------------------------------------------- /tests/testthat/brmfit.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacob-long/interactions/df22b3ddcf5f88c7608f0cd08ef25e60bcdf4b99/tests/testthat/brmfit.rds -------------------------------------------------------------------------------- /tests/testthat/rsafit.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacob-long/interactions/df22b3ddcf5f88c7608f0cd08ef25e60bcdf4b99/tests/testthat/rsafit.rds -------------------------------------------------------------------------------- /tests/testthat/test_sim_margins.R: -------------------------------------------------------------------------------- 1 | skip_if_not_installed("margins") 2 | context("sim_margins lm") 3 | 4 | states <- as.data.frame(state.x77) 5 | states$HSGrad <- states$`HS Grad` 6 | states$o70 <- 0 7 | states$o70[states$`Life Exp` > 70] <- 1 8 | states$o70n <- states$o70 9 | states$o70 <- factor(states$o70) 10 | states$o70l <- states$`Life Exp` > 70 11 | states$o70c <- ifelse(states$o70l, yes = "yes", no = "no") 12 | set.seed(3) 13 | states$wts <- runif(50, 0, 3) 14 | fit <- lm(Income ~ HSGrad*Murder*Illiteracy + o70 + Area, data = states) 15 | fit2 <- lm(Income ~ HSGrad*o70, data = states) 16 | fit2n <- lm(Income ~ HSGrad*o70n, data = states) 17 | fitw <- lm(Income ~ HSGrad*Murder*Illiteracy + o70 + Area, data = states, 18 | weights = wts) 19 | fitl <- lm(Income ~ HSGrad*o70l, data = states) 20 | fitc <- lm(Income ~ HSGrad*Murder + o70c, data = states) 21 | 22 | if (requireNamespace("survey")) { 23 | suppressMessages(library(survey, quietly = TRUE)) 24 | data(api) 25 | dstrat <- svydesign(id = ~1, strata = ~stype, weights = ~pw, data = apistrat, 26 | fpc = ~fpc) 27 | regmodel <- svyglm(api00 ~ ell * meals * both + sch.wide, design = dstrat) 28 | } 29 | 30 | test_that("sim_margins works for lm", { 31 | expect_s3_class(sim_margins(model = fit, 32 | pred = Murder, 33 | modx = Illiteracy, 34 | mod2 = HSGrad), "sim_margins") 35 | expect_s3_class(sim_margins(model = fit, 36 | pred = Murder, 37 | modx = Illiteracy, 38 | mod2 = HSGrad, 39 | vce = "bootstrap", 40 | iterations = 50), "sim_margins") 41 | expect_s3_class(sim_margins(model = fit, 42 | pred = Murder, 43 | modx = Illiteracy, 44 | mod2 = HSGrad, 45 | vce = "simulation", 46 | iterations = 50), "sim_margins") 47 | }) 48 | 49 | test_that("sim_margins works for weighted lm", { 50 | expect_s3_class(sim_margins(model = fitw, 51 | pred = Murder, 52 | modx = Illiteracy, 53 | mod2 = HSGrad, 54 | modx.values = c(1.0, 1.5, 2.0)), class = "sim_margins") 55 | expect_s3_class(sim_margins(model = fitw, 56 | pred = Murder, 57 | modx = Illiteracy, 58 | mod2 = HSGrad, 59 | vce = "bootstrap", 60 | modx.values = c(1.0, 1.5, 2.0), 61 | iterations = 50), class = "sim_margins") 62 | expect_s3_class(sim_margins(model = fitw, 63 | pred = Murder, 64 | modx = Illiteracy, 65 | mod2 = HSGrad, 66 | vce = "simulation", 67 | modx.values = c(1.0, 1.5, 2.0), 68 | iterations = 50), class = "sim_margins") 69 | }) 70 | 71 | test_that("sim_margins works for lm w/ logical", { 72 | expect_s3_class(sim_margins(model = fitl, 73 | pred = HSGrad, 74 | modx = o70l), "sim_margins") 75 | expect_s3_class(sim_margins(model = fitl, 76 | pred = HSGrad, 77 | modx = o70l, 78 | vce = "bootstrap", 79 | iterations = 50), "sim_margins") 80 | expect_s3_class(sim_margins(model = fitl, 81 | pred = HSGrad, 82 | modx = o70l, 83 | vce = "simulation", 84 | iterations = 50), "sim_margins") 85 | }) 86 | 87 | test_that("sim_margins works for lm w/ non-focal character", { 88 | expect_s3_class(sim_margins(model = fitc, 89 | pred = HSGrad, 90 | modx = Murder), "sim_margins") 91 | expect_s3_class(sim_margins(model = fitc, 92 | pred = HSGrad, 93 | modx = Murder, 94 | vce = "bootstrap", 95 | iterations = 50), "sim_margins") 96 | expect_s3_class(sim_margins(model = fitc, 97 | pred = HSGrad, 98 | modx = Murder, 99 | vce = "simulation", 100 | iterations = 50), "sim_margins") 101 | }) 102 | 103 | context("sim_margins methods") 104 | 105 | test_that("as_huxtable.sim_margins works", { 106 | skip_if_not_installed("huxtable") 107 | skip_if_not_installed("broom") 108 | ss3 <- sim_margins(model = fit, pred = Murder, modx = Illiteracy, 109 | mod2 = HSGrad) 110 | ss <- sim_margins(model = fit, pred = Murder, modx = Illiteracy) 111 | expect_is(as_huxtable.sim_margins(ss3), "huxtable") 112 | expect_is(as_huxtable.sim_margins(ss), "huxtable") 113 | ss3 <- sim_margins(model = fit, pred = Murder, modx = Illiteracy, 114 | mod2 = HSGrad, vce = "bootstrap", iterations = 50) 115 | ss <- sim_margins(model = fit, pred = Murder, modx = Illiteracy, 116 | vce = "bootstrap", iterations = 50) 117 | expect_is(as_huxtable.sim_margins(ss3), "huxtable") 118 | expect_is(as_huxtable.sim_margins(ss), "huxtable") 119 | ss3 <- sim_margins(model = fit, pred = Murder, modx = Illiteracy, 120 | mod2 = HSGrad, vce = "simulation", iterations = 50) 121 | ss <- sim_margins(model = fit, pred = Murder, modx = Illiteracy, 122 | vce = "simulation", iterations = 50) 123 | expect_is(as_huxtable.sim_margins(ss3), "huxtable") 124 | expect_is(as_huxtable.sim_margins(ss), "huxtable") 125 | }) 126 | 127 | test_that("plot.sim_margins works", { 128 | skip_if_not_installed("broom.mixed") 129 | skip_if_not_installed("broom") 130 | ss3 <- sim_margins(model = fit, pred = Murder, modx = Illiteracy, 131 | mod2 = HSGrad) 132 | ss <- sim_margins(model = fit, pred = Murder, modx = Illiteracy) 133 | expect_is(plot(ss3), "ggplot") 134 | expect_is(plot(ss), "ggplot") 135 | ss3 <- sim_margins(model = fit, pred = Murder, modx = Illiteracy, 136 | mod2 = HSGrad, vce = "bootstrap", iterations = 50) 137 | ss <- sim_margins(model = fit, pred = Murder, modx = Illiteracy, 138 | vce = "bootstrap", iterations = 50) 139 | expect_is(plot(ss3), "ggplot") 140 | expect_is(plot(ss), "ggplot") 141 | ss3 <- sim_margins(model = fit, pred = Murder, modx = Illiteracy, 142 | mod2 = HSGrad, vce = "simulation", iterations = 50) 143 | ss <- sim_margins(model = fit, pred = Murder, modx = Illiteracy, 144 | vce = "simulation", iterations = 50) 145 | expect_is(plot(ss3), "ggplot") 146 | expect_is(plot(ss), "ggplot") 147 | }) 148 | 149 | context("sim_margins svyglm") 150 | 151 | test_that("sim_margins works for svyglm", { 152 | skip_if_not_installed("survey") 153 | expect_is(sim_margins(regmodel, pred = ell, modx = meals, mod2 = both), 154 | "sim_margins") 155 | # margins bug 156 | # expect_is(sim_margins(regmodel, pred = ell, modx = meals, mod2 = both, 157 | # vce = "bootstrap", iterations = 50), 158 | # "sim_margins") 159 | # expect_is(sim_margins(regmodel, pred = ell, modx = meals, mod2 = both, 160 | # vce = "simulation", iterations = 50), 161 | # "sim_margins") 162 | }) 163 | 164 | context("sim_margins merMod") 165 | 166 | test_that("sim_margins works for lme4", { 167 | skip_if_not_installed("lme4") 168 | library(lme4, quietly = TRUE) 169 | data(VerbAgg) 170 | fmVA0 <- glmer(r2 ~ Anger * Gender + btype + situ + (1|id) + (1|item), 171 | family = binomial, data = VerbAgg, nAGQ=0L) 172 | lmVA0 <- lmer(as.numeric(r2 == "Y") ~ Anger * Gender + btype + situ + 173 | (1|id) + (1|item), data = VerbAgg) 174 | 175 | expect_is(sim_margins(lmVA0, pred = Anger, modx = Gender), "sim_margins") 176 | expect_is(sim_margins(fmVA0, pred = Anger, modx = Gender), "sim_margins") 177 | }) 178 | -------------------------------------------------------------------------------- /tests/testthat/test_sim_slopes.R: -------------------------------------------------------------------------------- 1 | context("sim_slopes lm") 2 | 3 | states <- as.data.frame(state.x77) 4 | states$HSGrad <- states$`HS Grad` 5 | states$o70 <- 0 6 | states$o70[states$`Life Exp` > 70] <- 1 7 | states$o70n <- states$o70 8 | states$o70 <- factor(states$o70) 9 | states$o70l <- states$`Life Exp` > 70 10 | states$o70c <- ifelse(states$o70l, yes = "yes", no = "no") 11 | set.seed(3) 12 | states$wts <- runif(50, 0, 3) 13 | fit <- lm(Income ~ HSGrad*Murder*Illiteracy + o70 + Area, data = states) 14 | fit2 <- lm(Income ~ HSGrad*o70, data = states) 15 | fit2n <- lm(Income ~ HSGrad*o70n, data = states) 16 | fitw <- lm(Income ~ HSGrad*Murder*Illiteracy + o70 + Area, data = states, 17 | weights = wts) 18 | fitl <- lm(Income ~ HSGrad*o70l, data = states) 19 | fitc <- lm(Income ~ HSGrad*Murder + o70c, data = states) 20 | 21 | library(ggplot2) 22 | diamond <- diamonds 23 | diamond <- diamond[diamond$color != "D",] 24 | set.seed(10) 25 | samps <- sample(1:nrow(diamond), 2000) 26 | diamond <- diamond[samps,] 27 | fitd <- lm(price ~ cut * color * clarity, data = diamond) 28 | 29 | if (requireNamespace("survey")) { 30 | suppressMessages(library(survey, quietly = TRUE)) 31 | data(api) 32 | dstrat <- svydesign(id = ~1, strata = ~stype, weights = ~pw, data = apistrat, 33 | fpc = ~fpc) 34 | regmodel <- svyglm(api00 ~ ell * meals * both + sch.wide, design = dstrat) 35 | } 36 | 37 | test_that("sim_slopes works for lm", { 38 | expect_silent(sim_slopes(model = fit, 39 | pred = Murder, 40 | modx = Illiteracy, 41 | mod2 = HSGrad, 42 | centered = "all")) 43 | expect_warning(sim_slopes(model = fit, 44 | pred = Murder, 45 | modx = Illiteracy, 46 | mod2 = HSGrad, 47 | centered = "HSGrad")) 48 | }) 49 | 50 | test_that("sim_slopes works for weighted lm", { 51 | # Out of range warning 52 | expect_warning(sim_slopes(model = fitw, 53 | pred = Murder, 54 | modx = Illiteracy, 55 | mod2 = HSGrad, 56 | centered = "all")) 57 | expect_s3_class(suppressWarnings(sim_slopes(model = fitw, 58 | pred = Murder, 59 | modx = Illiteracy, 60 | mod2 = HSGrad, 61 | centered = "all")), class = "sim_slopes") 62 | }) 63 | 64 | test_that("sim_slopes works for lm w/ logical", { 65 | expect_silent(sim_slopes(model = fitl, 66 | pred = HSGrad, 67 | modx = o70l, 68 | johnson_neyman = FALSE)) 69 | }) 70 | 71 | test_that("sim_slopes works for lm w/ non-focal character", { 72 | expect_silent(sim_slopes(model = fitc, 73 | pred = HSGrad, 74 | modx = Murder, 75 | johnson_neyman = FALSE)) 76 | }) 77 | 78 | test_that("sim_slopes accepts categorical predictor", { 79 | expect_warning(ss <- sim_slopes(fitd, pred = cut, modx = color)) 80 | expect_s3_class(ss, "sim_slopes") 81 | expect_warning(ss <- sim_slopes(fitd, pred = cut, modx = color, mod2 = clarity)) 82 | expect_s3_class(ss, "sim_slopes") 83 | }) 84 | 85 | context("sim_slopes methods") 86 | 87 | test_that("as_huxtable.sim_slopes works", { 88 | skip_if_not_installed("huxtable") 89 | skip_if_not_installed("broom") 90 | ss3 <- sim_slopes(model = fit, pred = Murder, modx = Illiteracy, 91 | mod2 = HSGrad) 92 | ss <- sim_slopes(model = fit, pred = Murder, modx = Illiteracy) 93 | expect_is(as_huxtable.sim_slopes(ss3), "huxtable") 94 | expect_is(as_huxtable.sim_slopes(ss), "huxtable") 95 | }) 96 | test_that("plot.sim_slopes works", { 97 | skip_if_not_installed("broom.mixed") 98 | skip_if_not_installed("broom") 99 | ss3 <- sim_slopes(model = fit, pred = Murder, modx = Illiteracy, 100 | mod2 = HSGrad) 101 | ss <- sim_slopes(model = fit, pred = Murder, modx = Illiteracy) 102 | expect_is(plot(ss3), "ggplot") 103 | expect_is(plot(ss), "ggplot") 104 | }) 105 | 106 | context("sim_slopes svyglm") 107 | 108 | test_that("sim_slopes works for svyglm", { 109 | skip_if_not_installed("survey") 110 | expect_is(sim_slopes(regmodel, pred = ell, modx = meals, mod2 = both, 111 | centered = "all"), "sim_slopes") 112 | }) 113 | 114 | context("sim_slopes merMod") 115 | 116 | test_that("sim_slopes works for lme4", { 117 | skip_if_not_installed("lme4") 118 | library(lme4, quietly = TRUE) 119 | data(VerbAgg) 120 | fmVA0 <- glmer(r2 ~ Anger * Gender + btype + situ + (1|id) + (1|item), 121 | family = binomial, data = VerbAgg, nAGQ=0L) 122 | lmVA0 <- lmer(as.numeric(r2 == "Y") ~ Anger * Gender + btype + situ + 123 | (1|id) + (1|item), data = VerbAgg) 124 | 125 | expect_is(sim_slopes(lmVA0, pred = Anger, modx = Gender, 126 | johnson_neyman = FALSE, t.df = "residual"), 127 | "sim_slopes") 128 | expect_is(sim_slopes(fmVA0, pred = Anger, modx = Gender, 129 | johnson_neyman = FALSE), "sim_slopes") 130 | }) 131 | 132 | 133 | ### johnson_neyman ########################################################### 134 | 135 | context("j_n specific") 136 | 137 | test_that("johnson_neyman control.fdr argument works", { 138 | expect_s3_class(johnson_neyman(fit, pred = Murder, modx = Illiteracy, 139 | control.fdr = TRUE), "johnson_neyman") 140 | }) 141 | 142 | test_that("johnson_neyman critical.t argument works", { 143 | expect_s3_class(johnson_neyman(fit, pred = Murder, modx = Illiteracy, 144 | critical.t = 2.1), "johnson_neyman") 145 | }) 146 | 147 | test_that("johnson_neyman color arguments work", { 148 | expect_silent(johnson_neyman(fit, pred = Murder, modx = Illiteracy, 149 | sig.color = "black", insig.color = "grey")$plot) 150 | }) 151 | 152 | test_that("johnson_neyman mod.range argument works", { 153 | expect_silent(johnson_neyman(fit, pred = Murder, modx = Illiteracy, 154 | mod.range = c(1, 2))$plot) 155 | }) 156 | -------------------------------------------------------------------------------- /vignettes/categorical.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Plotting interactions among categorical variables in regression models" 3 | author: "Jacob Long" 4 | date: "`r Sys.Date()`" 5 | output: html_vignette 6 | vignette: > 7 | %\VignetteIndexEntry{Plotting interactions among categorical variables in regression models} 8 | %\VignetteEngine{knitr::rmarkdown} 9 | %\VignetteEncoding{UTF-8} 10 | --- 11 | 12 | ```{r echo=FALSE} 13 | knitr::opts_chunk$set(message = F, warning = F, fig.width = 6, fig.height = 5) 14 | library(jtools) 15 | library(interactions) 16 | ``` 17 | 18 | When trying to understand interactions between categorical predictors, 19 | the types of visualizations called for tend to differ from those for continuous 20 | predictors. For that (and some other) reasons, `interactions` offers support for 21 | these in `cat_plot` while continuous predictors (perhaps in interactions with 22 | categorical predictors) are dealt with in `interact_plot`, which has a separate 23 | vignette. 24 | 25 | To be clear... 26 | 27 | If all the predictors involved in the interaction are categorical, use 28 | `cat_plot`. You can also use `cat_plot` to explore the effect of a single 29 | categorical predictor. 30 | 31 | If one or more are continuous, use `interact_plot`. 32 | 33 | ## Simple two-way interaction 34 | 35 | First, let's prep some data. I'm going to make some slight changes to the 36 | `mpg` dataset from `ggplot2` for didactic purposes to drop a few factor 37 | levels that have almost no values (e.g., there are 5 cylinder engines?). 38 | 39 | ```{r} 40 | library(ggplot2) 41 | mpg2 <- mpg 42 | mpg2$cyl <- factor(mpg2$cyl) 43 | mpg2["auto"] <- "auto" 44 | mpg2$auto[mpg2$trans %in% c("manual(m5)", "manual(m6)")] <- "manual" 45 | mpg2$auto <- factor(mpg2$auto) 46 | mpg2["fwd"] <- "2wd" 47 | mpg2$fwd[mpg2$drv == "4"] <- "4wd" 48 | mpg2$fwd <- factor(mpg2$fwd) 49 | ## Drop the two cars with 5 cylinders (rest are 4, 6, or 8) 50 | mpg2 <- mpg2[mpg2$cyl != "5",] 51 | ## Fit the model 52 | fit3 <- lm(cty ~ cyl * fwd * auto, data = mpg2) 53 | ``` 54 | 55 | So basically what we're looking at here is an interaction between number of 56 | cylinders in the engine of some cars and whether the car has all-wheel drive or 57 | two-wheel drive. The DV is fuel mileage in the city. 58 | 59 | Here's summary output for our model: 60 | 61 | ```{r} 62 | library(jtools) # for summ() 63 | summ(fit3) 64 | ``` 65 | 66 | Let's see what happens using all the default arguments: 67 | 68 | ```{r} 69 | cat_plot(fit3, pred = cyl, modx = fwd) 70 | ``` 71 | 72 | This is with `geom = "point"`. We can see a main effect of `cyl` and maybe 73 | something is going on with the interaction as well, since the different 74 | between `2wd` and `4wd` seems to decrease as `cyl` gets higher. 75 | 76 | You can also plot the observed data on the plot: 77 | 78 | ```{r} 79 | cat_plot(fit3, pred = cyl, modx = fwd, plot.points = TRUE) 80 | ``` 81 | 82 | ## Line plots 83 | 84 | And since `cyl` does have a clear order, it might make more sense to connect 85 | those dots. Let's try `geom = "line"`: 86 | 87 | ```{r} 88 | cat_plot(fit3, pred = cyl, modx = fwd, geom = "line") 89 | ``` 90 | 91 | Okay, that makes the trend quite a bit clearer. 92 | 93 | You have some other options, too. Suppose you will need this plot to look 94 | good in black and white. Let's change the shape of those points for different 95 | values of the moderator. 96 | 97 | ```{r} 98 | cat_plot(fit3, pred = cyl, modx = fwd, geom = "line", point.shape = TRUE) 99 | ``` 100 | 101 | You can change the line patterns as well for more clarity. 102 | 103 | ```{r} 104 | cat_plot(fit3, pred = cyl, modx = fwd, geom = "line", point.shape = TRUE, 105 | vary.lty = TRUE) 106 | ``` 107 | 108 | You may also choose any color palette from `RColorBrewer` as well as several 109 | preset palettes available in `jtools`: 110 | 111 | ```{r} 112 | cat_plot(fit3, pred = cyl, modx = fwd, geom = "line", point.shape = TRUE, 113 | colors = "Set2") 114 | ``` 115 | 116 | Use `?jtools_colors` for more on your color options. 117 | 118 | ## Bar/dynamite plots 119 | 120 | Last but not least, you can also make bar charts, AKA dynamite plots. For 121 | many situations, these are not the best way to show your data, but I know it's 122 | what a lot of people are looking for. 123 | 124 | ```{r} 125 | cat_plot(fit3, pred = cyl, modx = fwd, geom = "bar") 126 | ``` 127 | 128 | The transparency of the fill color depends on the presence of the error bars 129 | and observed data points. 130 | 131 | ```{r} 132 | cat_plot(fit3, pred = cyl, modx = fwd, geom = "bar", interval = FALSE) 133 | ``` 134 | 135 | Now let's look with observed data: 136 | 137 | ```{r} 138 | cat_plot(fit3, pred = cyl, modx = fwd, geom = "bar", interval = FALSE, 139 | plot.points = TRUE) 140 | ``` 141 | --------------------------------------------------------------------------------