├── .Rbuildignore ├── .github ├── .gitignore ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── feature_request.yml ├── pull_request_template.md └── workflows │ ├── R-CMD-check.yaml │ ├── lint.yaml │ ├── pkgdown.yaml │ └── test-coverage.yaml ├── .gitignore ├── .lintr ├── CODE_OF_CONDUCT.md ├── DESCRIPTION ├── LICENSE ├── LICENSE.md ├── NAMESPACE ├── NEWS.md ├── R ├── calculate_epv.R ├── calculate_threat.R ├── epv_guide.R ├── globals.R ├── plot_comet.R ├── plot_convexhull.R ├── plot_heatmap.R ├── plot_pass.R ├── plot_passflow.R ├── plot_passnet.R ├── plot_pizza.R ├── plot_scatter.R ├── plot_shot.R ├── plot_sonar.R ├── plot_timeline.r ├── plot_trendline.R ├── plot_voronoi.R ├── sample_event_data_guide.R ├── sample_sbevent_data_guide.R ├── threat_guide.R └── utils.R ├── README.Rmd ├── README.md ├── _pkgdown.yml ├── codecov.yml ├── data-raw └── DATASET.R ├── data ├── EPVGrid.rda ├── SampleEventData.rda ├── SampleSBData.rda └── xTGrid.rda ├── docs ├── 404.html ├── CODE_OF_CONDUCT.html ├── LICENSE-text.html ├── LICENSE.html ├── apple-touch-icon-120x120.png ├── apple-touch-icon-152x152.png ├── apple-touch-icon-180x180.png ├── apple-touch-icon-60x60.png ├── apple-touch-icon-76x76.png ├── apple-touch-icon.png ├── articles │ ├── Guide_to_EPV.html │ ├── Guide_to_Exp_Threat.html │ ├── Guide_to_Pitch_Plots.html │ ├── Guide_to_PizzaPlots.html │ ├── Guide_to_Version_0-2-0.html │ └── index.html ├── authors.html ├── deps │ ├── bootstrap-5.1.0 │ │ ├── bootstrap.bundle.min.js │ │ ├── bootstrap.bundle.min.js.map │ │ └── bootstrap.min.css │ ├── bootstrap-5.1.3 │ │ ├── bootstrap.bundle.min.js │ │ ├── bootstrap.bundle.min.js.map │ │ └── bootstrap.min.css │ ├── data-deps.txt │ └── jquery-3.6.0 │ │ ├── jquery-3.6.0.js │ │ ├── jquery-3.6.0.min.js │ │ └── jquery-3.6.0.min.map ├── extra.css ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── index.html ├── link.svg ├── logo.png ├── news │ └── index.html ├── pkgdown.js ├── pkgdown.yml ├── pull_request_template.html ├── reference │ ├── EPVGrid.html │ ├── Rplot001.png │ ├── SampleEventData.html │ ├── SampleSBData.html │ ├── TestEventData.html │ ├── calculate_epv.html │ ├── calculate_threat.html │ ├── figures │ │ ├── Convexhull.png │ │ ├── Convexhull_JordiAlba.png │ │ ├── Opta_Passnet.png │ │ ├── Passnet_Opta.png │ │ ├── Passnet_SB.png │ │ ├── SB_Passnet.png │ │ ├── addimage.png │ │ ├── calculate_epv.png │ │ ├── calculate_threat.png │ │ ├── comp_pizza_cus1.png │ │ ├── comp_pizza_cus2.png │ │ ├── comp_pizza_plot.png │ │ ├── comp_pizza_plot_cus.png │ │ ├── ggshakeRhex-small.png │ │ ├── ggshakeRhex.png │ │ ├── ggshakeRlogo.png │ │ ├── levelone_predeploy.png │ │ ├── logo.png │ │ ├── pizza_plot.png │ │ ├── pizza_plot_comparison.png │ │ ├── pizza_plot_cus.png │ │ ├── plot_heatmap.png │ │ ├── plot_pass.png │ │ ├── plot_passflow.png │ │ ├── plot_shot.png │ │ ├── plot_sonar.png │ │ ├── plot_voronoi.png │ │ ├── plot_voronoi_fill.png │ │ ├── shakeRlogo.png │ │ └── xTGrid.png │ ├── hull_fun.html │ ├── index.html │ ├── plot_convexhull.html │ ├── plot_heatmap.html │ ├── plot_pass.html │ ├── plot_passflow.html │ ├── plot_passnet.html │ ├── plot_pizza.html │ ├── plot_scatter.html │ ├── plot_shot.html │ ├── plot_sonar.html │ ├── plot_timeline.html │ ├── plot_trendline.html │ ├── plot_voronoi.html │ ├── shift_column.html │ ├── text_wrap.html │ ├── viridis_d_pal.html │ ├── xTGrid.html │ └── zissou_pal.html ├── search.json └── sitemap.xml ├── ggshakeR.Rproj ├── inst ├── extdata │ ├── EPV.csv │ ├── SampleEventData.csv │ └── xT.csv ├── planning_guide.xlsx └── testdata │ ├── ilkay.RDS │ ├── laliga2022.RDS │ ├── nico.RDS │ ├── sbevents.RDS │ └── shot_data.rds ├── man ├── EPVGrid.Rd ├── SampleEventData.Rd ├── SampleSBData.Rd ├── calculate_epv.Rd ├── calculate_threat.Rd ├── figures │ ├── Convexhull.png │ ├── Convexhull_JordiAlba.png │ ├── Opta_Passnet.png │ ├── SB_Passnet.png │ ├── addimage.png │ ├── calculate_epv.png │ ├── calculate_threat.png │ ├── comp_pizza_plot.png │ ├── comp_pizza_plot_cus.png │ ├── ggshakeRhex-small.png │ ├── ggshakeRhex.png │ ├── ggshakeRlogo.png │ ├── levelone_predeploy.png │ ├── logo.png │ ├── pizza_plot.png │ ├── pizza_plot_cus.png │ ├── plot_heatmap.png │ ├── plot_pass.png │ ├── plot_passflow.png │ ├── plot_shot.png │ ├── plot_sonar.png │ ├── plot_voronoi.png │ ├── plot_voronoi_fill.png │ ├── shakeRlogo.png │ └── xTGrid.png ├── hull_fun.Rd ├── plot_convexhull.Rd ├── plot_heatmap.Rd ├── plot_pass.Rd ├── plot_passflow.Rd ├── plot_passnet.Rd ├── plot_pizza.Rd ├── plot_scatter.Rd ├── plot_shot.Rd ├── plot_sonar.Rd ├── plot_timeline.Rd ├── plot_trendline.Rd ├── plot_voronoi.Rd ├── shift_column.Rd ├── text_wrap.Rd ├── viridis_d_pal.Rd ├── xTGrid.Rd └── zissou_pal.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 │ └── test_functions.R └── vignettes ├── .gitignore ├── Guide_to_EPV.Rmd ├── Guide_to_Exp_Threat.Rmd ├── Guide_to_Pitch_Plots.Rmd ├── Guide_to_PizzaPlots.Rmd └── Guide_to_Version_0-2-0.Rmd /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^ggshakeR\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^data-raw$ 4 | ^README\.Rmd$ 5 | ^_pkgdown\.yml$ 6 | ^docs$ 7 | ^pkgdown$ 8 | ^LICENSE\.md$ 9 | ^\.github$ 10 | ^\.lintr$ 11 | ^lintr-generator\.R$ 12 | ^codecov\.yml$ 13 | ^CODE_OF_CONDUCT\.md$ 14 | -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report 3 | title: "[Bug]: " 4 | labels: ["bug"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | If you have a question, please ask on StackOverflow , 10 | or RStudio Community instead. 11 | 12 | Please make sure the bug comes from {ggshakeR} and not other packages 13 | such as {worldfootballR}, {understatr}, etc. 14 | - type: textarea 15 | id: what-happened 16 | attributes: 17 | label: What happened? 18 | value: | 19 | Please include a minimal reprex. The goal of a reprex is to make it as 20 | easy as possible for the {ggshakeR} team to recreate your problem so 21 | that we can fix it. If you've never heard of a reprex before, start by 22 | reading , and follow the advice 23 | further down the page. Please don't include the `sessionInfo()`, although 24 | we may ask for it later. 25 | 26 | (Please delete this section when you're ready to submit.) 27 | --- 28 | ggshakeR version: 29 | R version: 30 | --- 31 | Brief description of the problem 32 | 33 | ```r 34 | # insert reprex here 35 | ``` 36 | validations: 37 | required: true 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: File a feature request 3 | title: "[Feature Request]: " 4 | labels: ["enhancement"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thanks for taking the time to fill out this feature request! 10 | - type: textarea 11 | id: feature-request 12 | attributes: 13 | label: Suggest an idea or something that can be improved. 14 | value: | 15 | Brief description of the feature 16 | 17 | ```r 18 | # insert reprex here if applicable 19 | ``` 20 | validations: 21 | required: true 22 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | **Checklist** 2 | 3 | Before creating the Pull-Request, I... 4 | 5 | - [ ] Ran `devtools::document()` and `Install and Restart`. 6 | - [ ] Ran `devtools::check()` (GHA will do this but doesn't hurt to check yourself). 7 | - [ ] (If applicable) Checked changes made to website with `pkgdown::build_site()`. 8 | - [ ] Wrote commit message(s) throughout the time spent working on this branch with clear details of changes made. 9 | - [ ] Updated package version with `usethis::use_version()` (choosing one of "major", "minor", "patch", "dev" options) and wrote down changes made in `NEWS.md` 10 | - [ ] (If applicable) Created new tests for new functions or changes made to existing functions (Check `codecov`). 11 | 12 | After all of the Github Actions Checks have finished, I... 13 | 14 | - [ ] Checked `lintr` issues by going to the latest commit or the `Checks` tab. 15 | - [ ] Used `Squash & Merge` option. 16 | - [ ] Wrote in **detail** of all the changes made in the various commits on this branch. 17 | - [ ] Added a `keyword` like `closes #56`, `fixes #123` at the end of the message. 18 | - [ ] Merged the Pull-Request. 19 | - [ ] Deleted the branch. 20 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/master/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 | jobs: 12 | R-CMD-check: 13 | runs-on: ${{ matrix.config.os }} 14 | 15 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 16 | 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | config: 21 | - {os: macOS-latest, r: 'release'} 22 | - {os: windows-latest, r: 'release'} 23 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} 24 | - {os: ubuntu-latest, r: 'release'} 25 | - {os: ubuntu-latest, r: 'oldrel-1'} 26 | 27 | env: 28 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 29 | R_KEEP_PKG_SOURCE: yes 30 | 31 | steps: 32 | - uses: actions/checkout@v2 33 | 34 | - uses: r-lib/actions/setup-pandoc@v2 35 | 36 | - uses: r-lib/actions/setup-r@v2 37 | with: 38 | r-version: ${{ matrix.config.r }} 39 | http-user-agent: ${{ matrix.config.http-user-agent }} 40 | use-public-rspm: true 41 | 42 | - uses: r-lib/actions/setup-r-dependencies@v2 43 | with: 44 | extra-packages: rcmdcheck 45 | 46 | - uses: r-lib/actions/check-r-package@v2 47 | -------------------------------------------------------------------------------- /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | branches: 4 | - main 5 | - master 6 | 7 | name: lint-changed-files 8 | 9 | jobs: 10 | lint-changed-files: 11 | runs-on: ubuntu-20.04 12 | env: 13 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 14 | steps: 15 | - uses: actions/checkout@v2 16 | 17 | - uses: r-lib/actions/setup-r@v2 18 | 19 | - uses: r-lib/actions/setup-r-dependencies@v2 20 | with: 21 | extra-packages: lintr 22 | 23 | - uses: r-lib/actions/setup-r-dependencies@v2 24 | with: 25 | extra-packages: | 26 | any::gh 27 | any::purrr 28 | needs: check 29 | 30 | - name: Extract and lint files changed by this PR 31 | run: | 32 | files <- gh::gh("GET https://api.github.com/repos/abhiamishra/ggshakeR/pulls/${{ github.event.pull_request.number }}/files") 33 | changed_files <- purrr::map_chr(files, "filename") 34 | all_files <- list.files(recursive = TRUE) 35 | exclusions_list <- as.list(setdiff(all_files, changed_files)) 36 | lintr::lint_package(exclusions = exclusions_list) 37 | shell: Rscript {0} 38 | 39 | -------------------------------------------------------------------------------- /.github/workflows/pkgdown.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/master/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 | release: 7 | types: [published] 8 | workflow_dispatch: 9 | 10 | name: pkgdown 11 | 12 | jobs: 13 | pkgdown: 14 | runs-on: ubuntu-latest 15 | env: 16 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 17 | steps: 18 | - uses: actions/checkout@v2 19 | 20 | - uses: r-lib/actions/setup-pandoc@v2 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::pkgdown, local::. 29 | needs: website 30 | 31 | - name: Deploy package 32 | run: | 33 | git config --local user.name "$GITHUB_ACTOR" 34 | git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com" 35 | Rscript -e 'pkgdown::deploy_to_branch(new_process = FALSE)' 36 | -------------------------------------------------------------------------------- /.github/workflows/test-coverage.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/master/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | branches: [main, master] 8 | 9 | name: test-coverage 10 | 11 | jobs: 12 | test-coverage: 13 | runs-on: ubuntu-latest 14 | env: 15 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | 20 | - uses: r-lib/actions/setup-r@v2 21 | with: 22 | use-public-rspm: true 23 | 24 | - uses: r-lib/actions/setup-r-dependencies@v2 25 | with: 26 | extra-packages: covr 27 | 28 | - name: Test coverage 29 | run: covr::codecov() 30 | shell: Rscript {0} 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata -------------------------------------------------------------------------------- /.lintr: -------------------------------------------------------------------------------- 1 | linters: with_defaults( 2 | line_length_linter = NULL, 3 | object_name_linter = NULL, 4 | commented_code_linter = NULL, 5 | seq_linter = NULL, 6 | single_quotes_linter = NULL, 7 | object_usage_linter = NULL, 8 | trailing_whitespace_linter = NULL, 9 | cyclocomp_linter = NULL 10 | ) 11 | exclusions: list( 12 | "data/xTGrid.rda", 13 | "docs/404.html", 14 | "docs/articles/Guide_to_Exp_Threat.html", 15 | "docs/articles/Guide_to_Pitch_Plots.html", 16 | "docs/articles/Guide_to_PizzaPlots.html", 17 | "docs/articles/index.html", 18 | "docs/authors.html", 19 | "docs/deps/bootstrap-5.1.0/bootstrap.bundle.min.js", 20 | "docs/deps/bootstrap-5.1.0/bootstrap.bundle.min.js.map", 21 | "docs/deps/bootstrap-5.1.0/bootstrap.min.css", 22 | "docs/deps/data-deps.txt", 23 | "docs/deps/jquery-3.6.0/jquery-3.6.0.js", 24 | "docs/deps/jquery-3.6.0/jquery-3.6.0.min.js", 25 | "docs/deps/jquery-3.6.0/jquery-3.6.0.min.map", 26 | "docs/extra.css", 27 | "docs/index.html", 28 | "docs/LICENSE-text.html", 29 | "docs/LICENSE.html", 30 | "docs/link.svg", 31 | "docs/news/index.html", 32 | "docs/pkgdown.js", 33 | "docs/pkgdown.yml", 34 | "docs/reference/calculate_threat.html", 35 | "docs/reference/figures/addimage.png", 36 | "docs/reference/figures/calculate_threat.png", 37 | "docs/reference/figures/comp_pizza_cus1.png", 38 | "docs/reference/figures/comp_pizza_cus2.png", 39 | "docs/reference/figures/comp_pizza_plot.png", 40 | "docs/reference/figures/comp_pizza_plot_cus.png", 41 | "docs/reference/figures/ggshakeRhex-small.png", 42 | "docs/reference/figures/ggshakeRhex.png", 43 | "docs/reference/figures/ggshakeRlogo.png", 44 | "docs/reference/figures/levelone_predeploy.png", 45 | "docs/reference/figures/pizza_plot.png", 46 | "docs/reference/figures/pizza_plot_comparison.png", 47 | "docs/reference/figures/plot_heatmap.png", 48 | "docs/reference/figures/plot_passflow.png", 49 | "docs/reference/figures/plot_shot.png", 50 | "docs/reference/figures/plot_sonar.png", 51 | "docs/reference/figures/shakeRlogo.png", 52 | "docs/reference/figures/xTGrid.png", 53 | "docs/reference/index.html", 54 | "docs/reference/plot_heatmap.html", 55 | "docs/reference/plot_pass.html", 56 | "docs/reference/plot_passflow.html", 57 | "docs/reference/plot_pizza.html", 58 | "docs/reference/plot_scatter.html", 59 | "docs/reference/plot_shot.html", 60 | "docs/reference/plot_sonar.html", 61 | "docs/reference/plot_timeline.html", 62 | "docs/reference/plot_trendline.html", 63 | "docs/reference/Rplot001.png", 64 | "docs/reference/xTGrid.html", 65 | "docs/search.json", 66 | "docs/sitemap.xml", 67 | "man/calculate_threat.Rd", 68 | "man/figures/addimage.png", 69 | "man/figures/calculate_threat.png", 70 | "man/figures/comp_pizza_plot.png", 71 | "man/figures/comp_pizza_plot_cus.png", 72 | "man/figures/ggshakeRhex-small.png", 73 | "man/figures/ggshakeRhex.png", 74 | "man/figures/ggshakeRlogo.png", 75 | "man/figures/levelone_predeploy.png", 76 | "man/figures/pizza_plot.png", 77 | "man/figures/pizza_plot_comparison.png", 78 | "man/figures/plot_heatmap.png", 79 | "man/figures/plot_passflow.png", 80 | "man/figures/plot_shot.png", 81 | "man/figures/plot_sonar.png", 82 | "man/figures/shakeRlogo.png", 83 | "man/figures/xTGrid.png", 84 | "man/plot_heatmap.Rd", 85 | "man/plot_pass.Rd", 86 | "man/plot_passflow.Rd", 87 | "man/plot_pizza.Rd", 88 | "man/plot_scatter.Rd", 89 | "man/plot_shot.Rd", 90 | "man/plot_sonar.Rd", 91 | "man/plot_timeline.Rd", 92 | "man/plot_trendline.Rd", 93 | "man/xTGrid.Rd", 94 | "vignettes/Guide_to_Exp_Threat.Rmd", 95 | "vignettes/Guide_to_Pitch_Plots.Rmd", 96 | "vignettes/Guide_to_PizzaPlots.Rmd" 97 | ) 98 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the overall 26 | community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards 42 | of acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies 54 | when an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail 56 | address, posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at [INSERT CONTACT 63 | METHOD]. All complaints will be reviewed and investigated promptly and fairly. 64 | 65 | All community leaders are obligated to respect the privacy and security of the 66 | reporter of any incident. 67 | 68 | ## Enforcement Guidelines 69 | 70 | Community leaders will follow these Community Impact Guidelines in determining 71 | the consequences for any action they deem in violation of this Code of Conduct: 72 | 73 | ### 1. Correction 74 | 75 | **Community Impact**: Use of inappropriate language or other behavior deemed 76 | unprofessional or unwelcome in the community. 77 | 78 | **Consequence**: A private, written warning from community leaders, providing 79 | clarity around the nature of the violation and an explanation of why the 80 | behavior was inappropriate. A public apology may be requested. 81 | 82 | ### 2. Warning 83 | 84 | **Community Impact**: A violation through a single incident or series of 85 | actions. 86 | 87 | **Consequence**: A warning with consequences for continued behavior. No 88 | interaction with the people involved, including unsolicited interaction with 89 | those enforcing the Code of Conduct, for a specified period of time. This 90 | includes avoiding interactions in community spaces as well as external channels 91 | like social media. Violating these terms may lead to a temporary or permanent 92 | ban. 93 | 94 | ### 3. Temporary Ban 95 | 96 | **Community Impact**: A serious violation of community standards, including 97 | sustained inappropriate behavior. 98 | 99 | **Consequence**: A temporary ban from any sort of interaction or public 100 | communication with the community for a specified period of time. No public or 101 | private interaction with the people involved, including unsolicited interaction 102 | with those enforcing the Code of Conduct, is allowed during this period. 103 | Violating these terms may lead to a permanent ban. 104 | 105 | ### 4. Permanent Ban 106 | 107 | **Community Impact**: Demonstrating a pattern of violation of community 108 | standards, including sustained inappropriate behavior, harassment of an 109 | individual, or aggression toward or disparagement of classes of individuals. 110 | 111 | **Consequence**: A permanent ban from any sort of public interaction within the 112 | community. 113 | 114 | ## Attribution 115 | 116 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 117 | version 2.0, 118 | available at https://www.contributor-covenant.org/version/2/0/ 119 | code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at https:// 128 | www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: ggshakeR 2 | Title: Analytics and Visualization Package for Soccer Data 3 | Version: 0.2.0.9002 4 | Authors@R: c( 5 | person(given = "Abhishek Amol", 6 | family = "Mishra", 7 | role = c("aut", "cre"), 8 | email = "abhiamishra0@gmail.com"), 9 | person(given = "Harsh", family = "Krishna", 10 | role = c("aut")), 11 | person(given = "Ryo", family = "Nakagawara", 12 | role = c("aut"), 13 | email = "ryonakagawara@gmail.com") 14 | ) 15 | Description: ggshakeR is an analysis and visualization R package that works with publically available soccer data. The datasets (for now) include FBref, StatsBomb, and understat. 16 | License: MIT + file LICENSE 17 | Encoding: UTF-8 18 | LazyData: true 19 | Roxygen: list(markdown = TRUE) 20 | RoxygenNote: 7.2.3 21 | Imports: 22 | tidyr, 23 | magrittr, 24 | dplyr, 25 | ggplot2, 26 | ggsoccer, 27 | ggrepel, 28 | ggtext, 29 | TTR, 30 | stringi, 31 | purrr, 32 | ggforce, 33 | gridExtra 34 | Suggests: 35 | rmarkdown, 36 | knitr, 37 | testthat (>= 3.0.0), 38 | covr, 39 | stats 40 | Config/testthat/edition: 3 41 | Depends: 42 | R (>= 3.5.0) 43 | URL: https://github.com/abhiamishra/ggshakeR, 44 | https://abhiamishra.github.io/ggshakeR/ 45 | BugReports: https://github.com/abhiamishra/ggshakeR/issues 46 | VignetteBuilder: knitr 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Abhishek A. Mishra 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 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2021 Abhishek A. Mishra 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(calculate_epv) 4 | export(calculate_threat) 5 | export(plot_convexhull) 6 | export(plot_heatmap) 7 | export(plot_pass) 8 | export(plot_passflow) 9 | export(plot_passnet) 10 | export(plot_pizza) 11 | export(plot_scatter) 12 | export(plot_shot) 13 | export(plot_sonar) 14 | export(plot_timeline) 15 | export(plot_trendline) 16 | export(plot_voronoi) 17 | import(TTR) 18 | import(dplyr) 19 | import(ggplot2) 20 | import(ggrepel) 21 | import(ggsoccer) 22 | import(ggtext) 23 | import(purrr) 24 | import(tidyr) 25 | importFrom(dplyr,filter) 26 | importFrom(dplyr,slice) 27 | importFrom(ggforce,geom_voronoi_segment) 28 | importFrom(ggforce,geom_voronoi_tile) 29 | importFrom(ggplot2,aes) 30 | importFrom(ggplot2,arrow) 31 | importFrom(ggplot2,geom_bin2d) 32 | importFrom(ggplot2,geom_segment) 33 | importFrom(ggplot2,ggplot) 34 | importFrom(ggplot2,labs) 35 | importFrom(ggplot2,scale_fill_gradientn) 36 | importFrom(grDevices,chull) 37 | importFrom(gridExtra,tableGrob) 38 | importFrom(gridExtra,ttheme_minimal) 39 | importFrom(magrittr,"%>%") 40 | importFrom(stats,na.omit) 41 | importFrom(stats,quantile) 42 | importFrom(stringi,stri_c) 43 | importFrom(stringi,stri_trans_general) 44 | importFrom(stringi,stri_wrap) 45 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # ggshakeR 0.2.0.9002 2 | 3 | - Bug Fixes 4 | - Fixes bugs in the `plot_pizza` in the custom option of the comparison plot [issue #123](https://github.com/abhiamishra/ggshakeR/issues/123). 5 | 6 | # ggshakeR 0.2.0.9001 7 | 8 | - Bug Fixes 9 | - Fixed `plot_pizza` bugs caused by FBRef updates [issue #121](https://github.com/abhiamishra/ggshakeR/issues/121) 10 | - Changed stat selection to named stats instead of selecting row numbers, which change because of FBRef data changes. 11 | - Added method to de-duplicate some stats that were returned for multiple positions, causing errors. 12 | - Changed season default argument to "Last 365 Days Men's Big 5 Leagues, UCL, UEL" to match FBRef data for men's big 5 league players. (this makes the caption wider than before, so some have been split over two lines) 13 | - Replaced long `|`-statements with `%in%`. 14 | - Deleted some old unused code. 15 | 16 | # ggshakeR 0.2.0.9000 17 | 18 | - Bug Fixes 19 | - Fixed `plot_heatmap` verification assessment [issue #101](https://github.com/abhiamishra/ggshakeR/issues/101) 20 | - Fixed pkgdown build issues [issue #104](https://github.com/abhiamishra/ggshakeR/issues/104) 21 | - Fixed `plot_passnet` direction orientation [issue #108](https://github.com/abhiamishra/ggshakeR/issues/108) 22 | - Fixed `plot_heatmap` and `plot_passflow`direction orientation + `plot_pass`opta functionality was fixed.[issue #111](https://github.com/abhiamishra/ggshakeR/issues/111) 23 | - Added "Created using ggshakeR" to `plot_heatmap`, `plot_passflow`, and `plot_pass`[issue #110](https://github.com/abhiamishra/ggshakeR/issues/110) 24 | - Added "Created using ggshakeR" to the rest of the functions [issue #110](https://github.com/abhiamishra/ggshakeR/issues/110) 25 | - Converted `plot_pizza` to work with new FBRef data from Opta. [issue #118](https://github.com/abhiamishra/ggshakeR/issues/118) 26 | 27 | # ggshakeR 0.2.0 28 | 29 | - Standardized function argument and object names 30 | - Use snake_case: `binSize` to `bin_size`, `dataType` to `data_type`, etc. 31 | - Data arguments all conform to `data` 32 | - Use US spelling: `color` rather than `colour`, etc. 33 | - Argument names made longer for clarity: `subt_size` to `subtitle_size`, `roll_avg` to `rolling_average`, etc. 34 | - See [issue #27](https://github.com/abhiamishra/ggshakeR/issues/27) 35 | - Created new functions 36 | - `plot_convexhull()` & `hull_fun()` utility function: See [issue #28](https://github.com/abhiamishra/ggshakeR/issues/28) 37 | - `plot_voronoi()` function: See [issue #28](https://github.com/abhiamishra/ggshakeR/issues/28) 38 | - `plot_passnet()` function: See [issue #31](https://github.com/abhiamishra/ggshakeR/issues/31), [issue #53](https://github.com/abhiamishra/ggshakeR/issues/53) 39 | - `calculate_epv()` function: See [issue #25](https://github.com/abhiamishra/ggshakeR/issues/25) 40 | - Created vignettes for new functions: See [issue #45](https://github.com/abhiamishra/ggshakeR/issues/45) 41 | - Improved functions 42 | - Improved `plot_pizza()`: See [issue #37](https://github.com/abhiamishra/ggshakeR/issues/37) 43 | - Added `jdp` pitch type option for `plot_heatmap()`: See [issue #37](https://github.com/abhiamishra/ggshakeR/issues/37) 44 | - Simplified `plot_pass()`: See [issue #64](https://github.com/abhiamishra/ggshakeR/issues/64) 45 | - Improved test coverage 46 | - See [issue #44](https://github.com/abhiamishra/ggshakeR/issues/44) 47 | - Other minor fixes 48 | - See [issue #21](https://github.com/abhiamishra/ggshakeR/issues/21), [issue #29](https://github.com/abhiamishra/ggshakeR/issues/29), [issue #32](https://github.com/abhiamishra/ggshakeR/issues/32), [issue #33](https://github.com/abhiamishra/ggshakeR/issues/33), [issue #47](https://github.com/abhiamishra/ggshakeR/issues/47), [issue #56](https://github.com/abhiamishra/ggshakeR/issues/56), [issue #58](https://github.com/abhiamishra/ggshakeR/issues/58), [issue #59](https://github.com/abhiamishra/ggshakeR/issues/59), [issue #62](https://github.com/abhiamishra/ggshakeR/issues/62), [issue #66](https://github.com/abhiamishra/ggshakeR/issues/66), [issue #76](https://github.com/abhiamishra/ggshakeR/issues/76), [issue #78](https://github.com/abhiamishra/ggshakeR/issues/78), [issue #81](https://github.com/abhiamishra/ggshakeR/issues/81), [issue #82](https://github.com/abhiamishra/ggshakeR/issues/82), [issue #88](https://github.com/abhiamishra/ggshakeR/issues/88), [issue #101](https://github.com/abhiamishra/ggshakeR/issues/101) 49 | 50 | # ggshakeR 0.1.2 51 | 52 | - Removed extraneous dependencies from package imports 53 | - See [issue #20](https://github.com/abhiamishra/ggshakeR/issues/20) 54 | 55 | # ggshakeR 0.1.1 56 | 57 | - Implemented Github Actions CI tools: 58 | - codecov test coverage checks 59 | - R Package Build checks 60 | - lintr checks 61 | - Build pkgdown website 62 | 63 | # ggshakeR 0.1.0 64 | 65 | - Added `plot_timeline()` function 66 | - Added vignettes: `Expected Treat`, `Pitch Plots`, `Pizza Plots` 67 | - Created pkgdown website 68 | - Added `plot_sonar()` function 69 | - Updated tests 70 | - Fixed `plot_pass()`, `plot_passflow()`, and `plot_heatmap()` functions to get plot positions on the proper side of the field 71 | - Package review (Ryo N.). See [issue #9](https://github.com/abhiamishra/ggshakeR/issues/9) 72 | - Various solutions to pass package checks. See [issue #12](https://github.com/abhiamishra/ggshakeR/issues/12) 73 | 74 | # ggshakeR 0.0.0.9000 75 | 76 | - Added a `NEWS.md` file to track changes to the package. 77 | -------------------------------------------------------------------------------- /R/calculate_epv.R: -------------------------------------------------------------------------------- 1 | #' Calculating EPV for passes, carries, etc 2 | #' 3 | #' @param data The dataframe that stores your data. Must contain starting x,y locations and ending x,y locations: `x`, `y`, `finalX`, `finalY` 4 | #' @param type indicator for what type of data the eventData. Currently, options include "opta" (default) and "statsbomb" 5 | #' @return returns a dataframe object 6 | #' 7 | #' @importFrom magrittr %>% 8 | #' @import dplyr 9 | #' @export 10 | #' 11 | #' @examples 12 | #' \dontrun{ 13 | #' endResult <- calculate_epv(test, type = "statsbomb") 14 | #' endResult 15 | #' } 16 | 17 | calculate_epv <- function(data, type = "opta") { 18 | if (nrow(data) > 0 && 19 | sum(x = c("x", "y", "finalX", "finalY") %in% names(data)) == 4) { 20 | copydata <- data 21 | 22 | copydata <- copydata %>% mutate(uniqueID = 1:nrow(copydata)) 23 | 24 | parsing <- copydata 25 | 26 | for (i in 1:length(names(parsing))) { 27 | if (names(parsing)[i] == "x") { 28 | names(parsing)[i] <- "x_col" 29 | #print(names(parsing)[i]) 30 | } 31 | 32 | if (names(parsing)[i] == "y") { 33 | names(parsing)[i] <- "y_col" 34 | } 35 | 36 | if (names(parsing)[i] == "finalX") { 37 | names(parsing)[i] <- "xend_col" 38 | } 39 | 40 | if (names(parsing)[i] == "finalY") { 41 | names(parsing)[i] <- "yend_col" 42 | } 43 | } 44 | 45 | parsing <- parsing %>% tidyr::drop_na(y_col) 46 | parsing <- parsing %>% tidyr::drop_na(x_col) 47 | parsing <- parsing %>% tidyr::drop_na(xend_col) 48 | parsing <- parsing %>% tidyr::drop_na(yend_col) 49 | 50 | if (type != "opta") { 51 | to_opta <- rescale_coordinates(from = pitch_statsbomb, to = pitch_opta) 52 | parsing$x <- to_opta$x(parsing$x_col) 53 | parsing$y <- to_opta$y(parsing$y_col) 54 | parsing$endX <- to_opta$x(parsing$xend_col) 55 | parsing$endY <- to_opta$y(parsing$yend_col) 56 | } else { 57 | parsing$x <- (parsing$x_col) 58 | parsing$y <- (parsing$y_col) 59 | parsing$endX <- (parsing$xend_col) 60 | parsing$endY <- (parsing$yend_col) 61 | } 62 | 63 | assign_epv <- function(a, b) { 64 | row <- 0 65 | col <- 0 66 | if (a %% 2 == 0) { 67 | if (b %% 3.125 == 0) { 68 | col <- as.integer(a / 2) 69 | row <- as.integer(b / 3.125) 70 | 71 | } else { 72 | col <- as.integer(a / 2) 73 | if (as.integer(b / 3.125) + 1 > 32) { 74 | row <- as.integer(b / 3.125) 75 | } else { 76 | row <- as.integer(b / 3.125) + 1 77 | } 78 | } 79 | } else { 80 | if (b %% 3.125 == 0) { 81 | if (as.integer(a / 2) + 1 > 50) { 82 | col <- as.integer(a / 2) 83 | } else { 84 | col <- as.integer(a / 2) + 1 85 | } 86 | row <- as.integer(b / 3.125) 87 | } else { 88 | if (as.integer(a / 2) + 1 > 50) { 89 | col <- as.integer(a / 2) 90 | } else { 91 | col <- as.integer(a / 2) + 1 92 | } 93 | 94 | if (as.integer(b / 3.125) + 1 > 32) { 95 | row <- as.integer(b / 3.125) 96 | } else { 97 | row <- as.integer(b / 3.125) + 1 98 | } 99 | } 100 | } 101 | return(EPVGrid[(33-row), col]) 102 | } 103 | 104 | parsing$EPVStart <- mapply(assign_epv, parsing$x, parsing$y) 105 | parsing$EPVEnd <- mapply(assign_epv, parsing$endX, parsing$endY) 106 | 107 | parsing <- parsing %>% 108 | select(-c(x, y, endX, endY)) 109 | 110 | for (i in 1:length(names(parsing))) { 111 | if (names(parsing)[i] == "x_col") { 112 | names(parsing)[i] <- "x" 113 | #print(names(parsing)[i]) 114 | } 115 | 116 | if (names(parsing)[i] == "y_col") { 117 | names(parsing)[i] <- "y" 118 | } 119 | 120 | if (names(parsing)[i] == "xend_col") { 121 | names(parsing)[i] <- "finalX" 122 | } 123 | 124 | if (names(parsing)[i] == "yend_col") { 125 | names(parsing)[i] <- "finalY" 126 | } 127 | } 128 | 129 | joined <- copydata %>% 130 | full_join(parsing, by = "uniqueID", suffix = c("", ".joined")) %>% 131 | select(-ends_with(".joined")) 132 | 133 | joined <- joined %>% 134 | select(-c("uniqueID")) 135 | 136 | joined$EPVStart[joined$EPVStart == "NULL"] <- -1 137 | joined$EPVEnd[joined$EPVEnd == "NULL"] <- -1 138 | 139 | joined$EPVStart <- as.numeric(joined$EPVStart) 140 | joined$EPVEnd <- as.numeric(joined$EPVEnd) 141 | 142 | return(joined) 143 | } else { 144 | stop("Dataframe has insufficient number of rows and/or you don't have the right amount of columns: x,y,finalX, finalY") 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /R/calculate_threat.R: -------------------------------------------------------------------------------- 1 | #' Calculating xT for passes, carries, etc 2 | #' 3 | #' @param data The dataframe that stores your data. Must contain starting x,y locations and ending x,y locations: `x`, `y`, `finalX`, `finalY` 4 | #' @param type indicator for what type of data the data. Currently, options include "opta" (default) and "statsbomb" 5 | #' @return returns a dataframe object 6 | #' 7 | #' @importFrom magrittr %>% 8 | #' @import dplyr 9 | #' @export 10 | #' 11 | #' @examples 12 | #' \dontrun{ 13 | #' endResult <- calculate_threat(test, type = "statsbomb") 14 | #' endResult 15 | #' } 16 | 17 | calculate_threat <- function(data, type = "opta") { 18 | if (nrow(data) > 0 && 19 | sum(c('x', 'y', 'finalX', 'finalY') %in% names(data)) == 4) { 20 | copydata <- data 21 | 22 | copydata <- copydata %>% mutate(uniqueID = 1:nrow(copydata)) 23 | 24 | parsing <- copydata 25 | 26 | for (i in 1:length(names(parsing))) { 27 | if (names(parsing)[i] == "x") { 28 | names(parsing)[i] <- "x_col" 29 | #print(names(parsing)[i]) 30 | } 31 | 32 | if (names(parsing)[i] == "y") { 33 | names(parsing)[i] <- "y_col" 34 | } 35 | 36 | if (names(parsing)[i] == "finalX") { 37 | names(parsing)[i] <- "xend_col" 38 | } 39 | 40 | if (names(parsing)[i] == "finalY") { 41 | names(parsing)[i] <- "yend_col" 42 | } 43 | } 44 | 45 | parsing <- parsing %>% tidyr::drop_na(y_col) 46 | parsing <- parsing %>% tidyr::drop_na(x_col) 47 | parsing <- parsing %>% tidyr::drop_na(xend_col) 48 | parsing <- parsing %>% tidyr::drop_na(yend_col) 49 | 50 | if (type != "opta") { 51 | to_opta <- rescale_coordinates(from = pitch_statsbomb, to = pitch_opta) 52 | parsing$x <- to_opta$x(parsing$x_col) 53 | parsing$y <- to_opta$y(parsing$y_col) 54 | parsing$endX <- to_opta$x(parsing$xend_col) 55 | parsing$endY <- to_opta$y(parsing$yend_col) 56 | } else { 57 | parsing$x <- (parsing$x_col) 58 | parsing$y <- (parsing$y_col) 59 | parsing$endX <- (parsing$xend_col) 60 | parsing$endY <- (parsing$yend_col) 61 | } 62 | 63 | assign_threat <- function(a, b) { 64 | row <- 0 65 | col <- 0 66 | if (a %% 8.33 == 0) { 67 | if (b %% 12.5 == 0) { 68 | col <- as.integer(a / 8.33) 69 | row <- as.integer(b / 12.5) 70 | 71 | } else { 72 | col <- as.integer(a / 8.33) 73 | if (as.integer(b / 12.5) + 1 > 8) { 74 | row <- as.integer(b / 12.5) 75 | } else { 76 | row <- as.integer(b / 12.5) + 1 77 | } 78 | } 79 | } else { 80 | if (b %% 12.5 == 0) { 81 | if (as.integer(a / 8.33) + 1 > 12) { 82 | col <- as.integer(a / 8.33) 83 | } else { 84 | col <- as.integer(a / 8.33) + 1 85 | } 86 | row <- as.integer(b / 12.5) 87 | } else { 88 | if (as.integer(a / 8.33) + 1 > 12) { 89 | col <- as.integer(a / 8.33) 90 | } else { 91 | col <- as.integer(a / 8.33) + 1 92 | } 93 | 94 | if (as.integer(b / 12.5) + 1 > 8) { 95 | row <- as.integer(b / 12.5) 96 | } else { 97 | row <- as.integer(b / 12.5) + 1 98 | } 99 | } 100 | } 101 | return(xTGrid[row, col]) 102 | } 103 | 104 | parsing$xTStart <- mapply(assign_threat, parsing$x, parsing$y) 105 | parsing$xTEnd <- mapply(assign_threat, parsing$endX, parsing$endY) 106 | 107 | parsing <- parsing %>% 108 | select(-c(x, y, endX, endY)) 109 | 110 | for (i in 1:length(names(parsing))) { 111 | if (names(parsing)[i] == "x_col") { 112 | names(parsing)[i] <- "x" 113 | } 114 | 115 | if (names(parsing)[i] == "y_col") { 116 | names(parsing)[i] <- "y" 117 | } 118 | 119 | if (names(parsing)[i] == "xend_col") { 120 | names(parsing)[i] <- "finalX" 121 | } 122 | 123 | if (names(parsing)[i] == "yend_col") { 124 | names(parsing)[i] <- "finalY" 125 | } 126 | } 127 | 128 | joined <- copydata %>% 129 | full_join(parsing, by = "uniqueID", suffix = c("", ".joined")) %>% 130 | select(-ends_with(".joined")) 131 | 132 | joined <- joined %>% 133 | select(-c("uniqueID")) 134 | 135 | joined$xTStart[joined$xTStart == "NULL"] <- -1 136 | joined$xTEnd[joined$xTEnd == "NULL"] <- -1 137 | 138 | joined$xTStart <- as.numeric(joined$xTStart) 139 | joined$xTEnd <- as.numeric(joined$xTEnd) 140 | 141 | return(joined) 142 | } else { 143 | stop("Dataframe has insufficient number of rows and/or you don't have the right amount of columns: `x`, `y`, `finalX`, `finalY`") 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /R/epv_guide.R: -------------------------------------------------------------------------------- 1 | #' @title EPV calculation data for analysis 2 | #' 3 | #' @description a simple \code{dataframe} that is used for 4 | #' calculating expected possession values 5 | #' 6 | #' @format A \code{dataframe} with 50 columns and 31 rows spanning 7 | #' a football pitch. 8 | #' 9 | "EPVGrid" 10 | -------------------------------------------------------------------------------- /R/globals.R: -------------------------------------------------------------------------------- 1 | utils::globalVariables( 2 | c("y_col", "x_col", "xend_col", "yend_col", "maindata", 3 | "j", "xTGrid", "x", "y", "endX", "endY", "..level..", 4 | "player.name", "team.name", "fname", "lname", "pass.outcome.name", 5 | "location.x", "location.y", "pass.end_location.x", "pass.end_location.y", 6 | "end", "start", "isProg", "pass.cross", "pass.shot_assist", "delta_y", "colorOutcome", 7 | "finalX", "finalY", "countPasses", "scouting_period", "Statistic", "Percentile", 8 | "Per90", "Player", "percentile", "X", "Y", "xG", "result", "isGoal", 9 | "ystart", "xstart", "slope", "startx", "starty", "pass.angle", "angle.round", 10 | "N", "n.angle", "maxN", "angle.norm", "pass.length", "frequency", "year", 11 | "season", "h_a", "home_team", "away_team", "home_away", "minute", "xGsum", 12 | "complete.cases", "Home", "Away", "Away_xG", "xGA", "xGSUM", "Date", 13 | "xGSM", "xGASM", "Home_xG", "distance", 14 | "events", "from", "outcome", "pass.recipient.name", 15 | "playerId", "player_label", "receiver", "teamId", 16 | "to", "type", "type.name", "xT", "xTEnd", 17 | "xTStart", "xend", "yend", "EPVGrid", "colour_b", 18 | "EPVEnd", "EPVStart", "EPV")) 19 | -------------------------------------------------------------------------------- /R/plot_comet.R: -------------------------------------------------------------------------------- 1 | # #' Plotting 2 | # #' 3 | # #' @param data The dataframe that stores your data. Must contain starting x,y locations and ending x,y locations 4 | # #' @param dataType indicator for what type of data the eventData. Currently, options include "opta" (default) and "statsbomb" 5 | # #' @param x_col name of the column that stores the starting x location 6 | # #' @param y_col name of the column that stores the starting y location 7 | # #' @param xend_col name of the column that stores the ending x location 8 | # #' @param yend_col name of the column that stores the ending x location 9 | # #' @return returns a ggplot2 object 10 | # #' 11 | # #' @importFrom magrittr %>% 12 | # #' @import dplyr 13 | # #' @import ggplot2 14 | # #' @import ggsoccer 15 | # #' @import mclust 16 | # #' @import ggforce 17 | # #' @import shadowtext 18 | # #' @export 19 | # #' 20 | # #' @examples endResult = calculate_threat(test, dataType = "statsbomb", x_col = "location.x", 21 | # #' y_col = "location.y", xend_col = "pass.end_location.x", 22 | # #' yend_col = "pass.end_location.y") 23 | # library(tidyverse) 24 | # library(dplyr) 25 | # library(ggplot2) 26 | # library(mclust) 27 | # library(ggforce) 28 | # library(shadowtext) 29 | # library(ggrepel) 30 | # library(worldfootballR) 31 | # library(viridis) 32 | 33 | # plot_comet <- function(data, start_year="", end_year="", 34 | # league=""){ 35 | 36 | # # Break out team and opponent tables 37 | # raw_team <- data %>% 38 | # filter(Team_or_Opponent=="team") 39 | # raw_opponent <- data %>% 40 | # filter(Team_or_Opponent=="opponent") 41 | # # Join team and opponent tables 42 | 43 | 44 | # fbref <- left_join(raw_team,raw_opponent,by=c("Squad","Season_End_Year")) 45 | # # Just the stuff we care about 46 | 47 | # scatter_x = "npxG_Per" 48 | # scatter_y = "npxG_Per" 49 | 50 | # scatterXFilter = paste(scatter_x, "x", sep=".") 51 | # scatterYFilter = paste(scatter_y, "y", sep=".") 52 | # fbref = fbref %>% select(Squad,Season=Season_End_Year,Comp=Comp.x,xPlot=scatterXFilter,yPlot=scatterYFilter) 53 | 54 | 55 | # ### Set some variables 56 | # # Pick number of clusters to highlight 57 | # #league = "La Liga" 58 | # n_clusters <- 5 59 | 60 | # ### Join data 61 | # startYearFilterX = paste("xPlot", start_year, sep="_") 62 | # startYearFilterY = paste("yPlot", start_year, sep="_") 63 | 64 | # endYearFilterX = paste("xPlot", end_year, sep="_") 65 | # endYearFilterY = paste("yPlot", end_year, sep="_") 66 | 67 | # endYearDiff = paste("diff", end_year, sep="_") 68 | # startYearDiff = paste("diff", start_year, sep="_") 69 | 70 | # pivoted <- 71 | # fbref %>% 72 | # filter(Comp==league) %>% 73 | # select(Squad,Season,Comp,xPlot,yPlot) %>% 74 | # pivot_wider(names_from=Season, values_from=c(xPlot,yPlot)) 75 | 76 | # for(i in 1:length(names(pivoted))){ 77 | # print(names(pivoted)[i]) 78 | # if(names(pivoted)[i] == startYearFilterX){ 79 | # names(pivoted)[i] = "startYearFilterX" 80 | # } 81 | 82 | # if(names(pivoted)[i] == startYearFilterY){ 83 | # names(pivoted)[i] = "startYearFilterY" 84 | # } 85 | 86 | # if(names(pivoted)[i] == endYearFilterX){ 87 | # names(pivoted)[i] = "endYearFilterX" 88 | # } 89 | 90 | # if(names(pivoted)[i] == endYearFilterY){ 91 | # names(pivoted)[i] = "endYearFilterY" 92 | # } 93 | # } 94 | 95 | # pivoted = pivoted %>% 96 | # mutate(endYearDiff=endYearFilterX-endYearFilterY, 97 | # startYearDiff=startYearFilterX-startYearFilterY, 98 | # diff_change=endYearDiff-startYearDiff) %>% 99 | # filter(!is.na(endYearDiff)) 100 | 101 | # ### Use GMM to define number of clusters selected up top 102 | # clusters <- Mclust(pivoted %>% 103 | # select(endYearFilterX,endYearFilterY,endYearDiff), 104 | # G=n_clusters) 105 | 106 | # ### Add clusters to data and we're ready to plot 107 | # plot_data <- bind_cols(pivoted,as.data.frame(clusters$classification)) %>% 108 | # mutate(cluster=as.character(paste0("cluster_",`clusters$classification`))) 109 | 110 | 111 | # plot_data$Squad = as.factor(plot_data$Squad) 112 | 113 | # c1 = plot_data %>% filter(`clusters$classification` == 1) 114 | # c2 = plot_data %>% filter(`clusters$classification` == 2) 115 | # c3 = plot_data %>% filter(`clusters$classification` == 3) 116 | # c4 = plot_data %>% filter(`clusters$classification` == 4) 117 | # c5 = plot_data %>% filter(`clusters$classification` == 5) 118 | 119 | 120 | # ### Draw plot 121 | # plot <- plot_data %>% 122 | # ggplot(aes(y=endYearFilterY,x=endYearFilterX))+ 123 | # # Colored ellipses around each cluster 124 | # geom_mark_ellipse(aes(fill=cluster),color=NA)+ 125 | # # Dotted lines indicating league average xG 126 | # geom_vline(xintercept=mean(plot_data$endYearFilterX),color="#4b4e43",linetype="dotted")+ 127 | # geom_hline(yintercept=mean(plot_data$endYearFilterY),color="#4b4e43",linetype="dotted")+ 128 | # # Comet tail 129 | # geom_link( 130 | # aes(y=startYearFilterY,x=startYearFilterX, 131 | # yend=endYearFilterY, xend=endYearFilterX, 132 | # alpha=stat(index),size=stat(index)))+ 133 | # # Outline circle in team color 134 | # geom_point( 135 | # aes(size=14, color=cluster, fill=cluster), shape=21, colour="black")+ 136 | # # This sets team colors 137 | # scale_color_identity() + 138 | # # Define axes 139 | # scale_y_reverse(breaks=seq(0.5, 3.5, by=0.5))+ 140 | # scale_x_continuous(breaks=seq(0.5, 3.5, by=0.5))+ 141 | # #Labels 142 | # labs(title=paste0("How are ",league ," clubs compared to last season?"), 143 | # subtitle="Comet tails lead from each team's location in ", start_year, "to its labeled point in ", end_year, ".", 144 | # y= "Non-Penalty Expected Goals Against", 145 | # x= "Non-Penalty Expected Goals For", 146 | # caption="@abhiamishra | Data: StatsBomb via FBref | Inspired by: @johnspacemuller ")+ 147 | # geom_label_repel(aes(label = Squad), force_pull = 0.3, force=2)+ 148 | # # Theme aesthetics 149 | # theme(plot.title=element_text(face = "bold", margin=margin(0,0,10,0),size=26), 150 | # plot.subtitle = element_text(margin=margin(0,0,10,0), size=16), 151 | # text = element_text(color="#4b4e43", margin=margin(0,0,0,0), size=14), 152 | # plot.caption = element_text(color="#4b4e43", margin=margin(10,0,0,0), size=10), 153 | # legend.position = "none", 154 | # legend.direction = "horizontal", 155 | # legend.margin = margin(0,0,0,0), 156 | # legend.title = element_text(margin=margin(0,0,10,0), size=14), 157 | # legend.text = element_blank(), 158 | # legend.background = element_rect(fill="#fffbef", color="#fffbef"), 159 | # strip.text = element_text(color="#4b4e43", size=14, margin=margin(10,0,3,0)), 160 | # strip.background = element_blank(), 161 | # axis.title.x = element_text(color="#4b4e43", size=18, margin=margin(10,0,0,0)), 162 | # axis.title.y = element_text(color="#4b4e43", size=18, margin=margin(0,10,0,0)), 163 | # axis.text.x = element_text(color="#4b4e43", margin=margin(10,0,5,0), size=14, angle=45, hjust=1), 164 | # axis.text.y = element_text(color="#4b4e43", margin=margin(0,5,0,0), size=14), 165 | # panel.spacing = unit(0.1,'in'), 166 | # panel.grid.major = element_blank(), 167 | # panel.grid.minor = element_blank(), 168 | # panel.background = element_rect(fill="#fffbef", color="#fffbef"), 169 | # plot.background = element_rect(fill="#fffbef", color="#fffbef"), 170 | # panel.border = element_rect(colour = "#4b4e43", fill=NA, size=0.5), 171 | # plot.margin = margin(0.3,0.3,0.3,0.3,"in")) 172 | # plot 173 | 174 | # } 175 | 176 | 177 | # plot = plot_comet(start_year = 2021, end_year = 2022, 178 | # league = "Premier League") 179 | # plot 180 | -------------------------------------------------------------------------------- /R/plot_convexhull.R: -------------------------------------------------------------------------------- 1 | #' Function for plotting convex hulls 2 | #' 3 | #' This function allows for data, that can be from Opta or StatsBomb, to be used 4 | #' for plotting convex hulls on top of an outline of a football pitch. 5 | #' 6 | #' @param data Data frame that houses pass data. Opta dataframe must contain atleast the following columns: `x`, `y`, `finalX`, `finalY`, `playerId` 7 | #' @param data_type Type of data that is being put in: opta or statsbomb. Default set to "statsbomb" 8 | #' @param color The color of the outline of the convex hull 9 | #' @param title Title of the plot 10 | #' @param theme Indicates what theme the map must be shown in: dark (default), white, rose, almond 11 | #' @return a ggplot2 object 12 | #' 13 | #' @import dplyr 14 | #' @import tidyr 15 | #' @import ggplot2 16 | #' @import ggsoccer 17 | #' @import purrr 18 | #' 19 | #' @export 20 | #' 21 | #' @examples 22 | #' \dontrun{ 23 | #' plot <- plot_convexhull(data = data, data_type = "opta", color = "blue", title = "Team 1") 24 | #' plot 25 | #' } 26 | 27 | plot_convexhull <- function(data, data_type = "statsbomb", 28 | color = "#E74C3C", title = "", theme = "dark") { 29 | 30 | if (data_type == "opta") { 31 | if (nrow(data) > 0 && 32 | sum(x = c("x", "y", "finalX", "finalY", "playerId") %in% names(data)) == 5) { 33 | } else { 34 | print(c("x", "y", "finalX", "finalY", "playerId")) 35 | stop("The dataset has insufficient columns and/or insufficient data.") 36 | } 37 | 38 | to_sb <- rescale_coordinates(from = pitch_opta, to = pitch_statsbomb) 39 | data$x <- to_sb$x(data$x) 40 | data$y <- to_sb$y(data$y) 41 | 42 | data <- data %>% 43 | drop_na(playerId, x, y) 44 | 45 | } else if (data_type == "statsbomb") { 46 | 47 | if (!"playerId" %in% colnames(data)) { 48 | data <- data %>% 49 | mutate(playerId = player.name) 50 | } 51 | 52 | data <- data %>% drop_na(playerId, x, y) 53 | } 54 | 55 | if (theme == "dark") { 56 | fill_b <- "#0d1117" 57 | colour_b <- "white" 58 | } else if (theme == "white") { 59 | fill_b <- "#F5F5F5" 60 | colour_b <- "black" 61 | } else if (theme == "rose") { 62 | fill_b <- "#FFE4E1" 63 | colour_b <- "#696969" 64 | } else if (theme == "almond") { 65 | fill_b <- "#FFEBCD" 66 | colour_b <- "#696969" 67 | } 68 | 69 | list_data <- split(data, data$playerId) 70 | 71 | hull_data <- list_data %>% 72 | purrr::map(hull_fun) %>% 73 | purrr::reduce(full_join) 74 | 75 | if (title == "") { 76 | title <- "Convex Hulls" 77 | } 78 | 79 | plotCaption <- "Created using ggshakeR" 80 | 81 | convex_hull <- ggplot(hull_data) + 82 | annotate_pitch(dimensions = pitch_statsbomb, fill = fill_b, colour = colour_b) + 83 | theme_pitch() + 84 | geom_point(data = data, aes(x = x, y = y), alpha = 0.5, colour = colour_b) + 85 | geom_polygon(aes(x = x, y = y), colour = color, alpha = 0.2, fill = color, size = 1) + 86 | facet_wrap(~playerId) + 87 | labs(title = title, 88 | x = "Direction of play faces rightward", 89 | caption = plotCaption) + 90 | theme(plot.background = element_rect(fill = fill_b, colour = NA), 91 | panel.background = element_rect(fill = fill_b, colour = NA), 92 | strip.background = element_rect(fill = fill_b, colour = NA), 93 | strip.text = element_text(colour = colour_b, size = 10), 94 | plot.title = element_text(colour = colour_b, size = 18, hjust = 0.5, face = "bold"), 95 | axis.title.x = element_text(colour = colour_b, size = 12, face = "bold")) 96 | 97 | if (data_type == "statsbomb") { 98 | convex_hull <- convex_hull + 99 | scale_y_reverse() 100 | } else if (data_type == "opta") { 101 | convex_hull 102 | } 103 | 104 | return(convex_hull) 105 | } 106 | -------------------------------------------------------------------------------- /R/plot_heatmap.R: -------------------------------------------------------------------------------- 1 | #' Plotting heatmap 2 | #' 3 | #' This function allows you to plot various types of heatmaps of starting x and y coordinates: 4 | #' hex binwidth heatmap, 5 | #' density heatmap, and 6 | #' binwidth heatmap 7 | #' 8 | #' @param data The dataframe that stores your data. Dataframe must contain atleast the following columns: `x`, `y`. 9 | #' @param type indicates the type of heatmap to plot. "hex" indicates hex bins, "density" indicates density (default), 10 | #' "binwidth" indicates binwidth heatmap pass, and "jdp" indicates a binned heatmap according jdp pitch markings. 11 | #' @param data_type Type of data that is being put in: opta or statsbomb. Default set to "statsbomb" 12 | #' @param binwidth indicates the size of the bin width to construct heatmap for type "binwidth". The same argument name as the underlying call to `geom_bin2d()`. Default set to 20. 13 | #' @param theme indicates what theme the map must be shown in: dark (default), white, rose, almond 14 | #' @return returns a ggplot2 object 15 | #' 16 | #' @importFrom magrittr %>% 17 | #' @import dplyr 18 | #' @import ggplot2 19 | #' @import ggsoccer 20 | #' @export 21 | #' 22 | #' @examples 23 | #' \dontrun{ 24 | #' plot <- plot_heatmap(data = touchData, type = "hex") 25 | #' plot 26 | #' } 27 | 28 | plot_heatmap <- function(data, type = "", data_type = "statsbomb", binwidth = 20, theme = "") { 29 | 30 | if (nrow(data) > 0 && 31 | sum(x = c("x", "y") %in% names(data)) == 2) { 32 | 33 | if (data_type == "opta") { 34 | to_sb <- rescale_coordinates(from = pitch_opta, to = pitch_statsbomb) 35 | data$x <- to_sb$x(data$x) 36 | data$y <- to_sb$y(data$y) 37 | } 38 | 39 | plot <- data %>% 40 | ggplot(aes(x = x, y = y)) 41 | 42 | if (theme == "dark" || theme == "") { 43 | fill_b <- "#0d1117" 44 | color_b <- "white" 45 | } else if (theme == "white") { 46 | fill_b <- "#F5F5F5" 47 | color_b <- "black" 48 | } else if (theme == "rose") { 49 | fill_b <- "#FFE4E1" 50 | color_b <- "#696969" 51 | } else if (theme == "almond") { 52 | fill_b <- "#FFEBCD" 53 | color_b <- "#696969" 54 | } 55 | plot <- plot + annotate_pitch(dimensions = pitch_statsbomb, colour = color_b, 56 | fill = fill_b) + 57 | theme_pitch() + 58 | theme(panel.background = element_rect(fill = fill_b)) 59 | 60 | if (type == "" || type == "density") { 61 | plot <- plot + 62 | stat_density_2d(aes(x = x, y = y, fill = ..level..), geom = "polygon") 63 | } else if (type == "hex") { 64 | plot <- plot + 65 | geom_hex(aes(x = x, y = y)) 66 | } else if (type == "binwidth") { 67 | plot <- plot + 68 | geom_bin2d(aes(x = x, y = y), 69 | binwidth = c(binwidth, binwidth), 70 | alpha = 0.9) 71 | } else if (type == "jdp") { 72 | 73 | binX1 <- c(0, 18, 39, 60, 81, 102, 120) 74 | binY1 <- c(0, 18) 75 | 76 | binX2 <- c(0, 18, 39, 60, 81, 102, 120) 77 | binY2 <- c(62, 80) 78 | 79 | binX3 <- c(18, 60, 102) 80 | binY3 <- c(18, 30, 50, 62) 81 | 82 | binX4 <- c(0, 18) 83 | binY4 <- c(18, 62) 84 | 85 | binX5 <- c(102, 120) 86 | binY5 <- c(18, 62) 87 | 88 | plot <- plot + 89 | geom_bin_2d(breaks = list(binX1, binY1), color = color_b, alpha = 0.9) + 90 | geom_bin_2d(breaks = list(binX2, binY2), color = color_b, alpha = 0.9) + 91 | geom_bin_2d(breaks = list(binX3, binY3), color = color_b, alpha = 0.9) + 92 | geom_bin_2d(breaks = list(binX4, binY4), color = color_b, alpha = 0.9) + 93 | geom_bin_2d(breaks = list(binX5, binY5), color = color_b, alpha = 0.9) 94 | } 95 | 96 | plotCaption <- "Created using ggshakeR" 97 | 98 | plot <- plot + 99 | scale_fill_continuous(type = "viridis") + 100 | labs( 101 | fill = "Density", 102 | caption = plotCaption 103 | ) 104 | 105 | return(plot) 106 | } else { 107 | stop("Please check that your data has the columns: 'x' and 'y'") 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /R/plot_pass.R: -------------------------------------------------------------------------------- 1 | #' Plotting passes 2 | #' 3 | #' This function allows you to plot various types of plots 4 | #' that have passes as some sort of input. Data entered must have columns for which you want to plot with. 5 | #' Compatible with StatsBomb and Opta data. 6 | #' 7 | #' @param data The data frame that stores your passing data. Opta data frame must contain at least the following columns: `x`, `y`, `finalX`, `finalY` 8 | #' @param data_type Type of data that is being put in: opta or statsbomb. Default set to "statsbomb" 9 | #' @param type indicates the type of plot to pass. "sep" separates successful and unsuccessful passes. "all" plots all passes on one pitch. Default = "sep". Only available for StatsBomb data 10 | #' @param progressive_pass indicates whether to map out progressive passes. Only available for StatsBomb data 11 | #' @param cross indicates whether to map out crosses. Only available for StatsBomb data 12 | #' @param shot indicates whether to map out shot assists. Only available for StatsBomb data 13 | #' @param switch indicates whether to map out switches of play. Only available for StatsBomb data 14 | #' @param outcome indicates whether you want successful ("suc"), unsuccessful ("unsuc"), or all ("all"). Only available for StatsBomb data 15 | #' @param theme indicates what theme the map must be shown in: dark (default), white, rose, almond 16 | #' @return returns a ggplot2 object 17 | #' 18 | #' @importFrom magrittr %>% 19 | #' @import dplyr 20 | #' @import ggplot2 21 | #' @import ggsoccer 22 | #' @export 23 | #' 24 | #' @examples 25 | #' \dontrun{ 26 | #' plot <- plot_pass(data, type = "all", progressive_pass = TRUE) 27 | #' plot 28 | #' } 29 | 30 | plot_pass <- function(data, data_type = "statsbomb", type = "sep", 31 | progressive_pass = FALSE, cross = FALSE, shot = FALSE, switch = FALSE, 32 | outcome = "all", theme = "dark") { 33 | 34 | if (theme == "dark" || theme == "") { 35 | fill_b <- "#0d1117" 36 | color_b <- "white" 37 | } else if (theme == "white") { 38 | fill_b <- "#F5F5F5" 39 | color_b <- "black" 40 | } else if (theme == "rose") { 41 | fill_b <- "#FFE4E1" 42 | color_b <- "#696969" 43 | } else if (theme == "almond") { 44 | fill_b <- "#FFEBCD" 45 | color_b <- "#696969" 46 | } 47 | 48 | plotCaption <- "Created using ggshakeR" 49 | 50 | if (data_type == "opta") { ## OPTA ---- 51 | 52 | if (nrow(data) <= 0 || 53 | sum(x = c("x", "y", "finalX", "finalY") %in% names(data)) != 4) { 54 | stop("The dataset has insufficient columns and/or insufficient data.") 55 | } 56 | 57 | to_sb <- rescale_coordinates(from = pitch_opta, to = pitch_statsbomb) 58 | 59 | data$x <- to_sb$x(data$x) 60 | data$y <- to_sb$y(data$y) 61 | data$finalX <- to_sb$x(data$finalX) 62 | data$finalY <- to_sb$y(data$finalY) 63 | 64 | if (progressive_pass == TRUE) { 65 | data <- data %>% 66 | mutate(start = sqrt((120 - x)^2 + (40 - y)^2)) %>% 67 | mutate(end = sqrt((120 - finalX)^2 + (40 - finalY)^2)) %>% 68 | mutate(isProg = ifelse(end <= 0.75 * start, 69 | 1, 70 | 0)) 71 | data <- data %>% filter(isProg == 1) 72 | } 73 | 74 | ### PLOT OPTA ---- 75 | plot <- ggplot(data = data) + 76 | annotate_pitch(dimensions = pitch_statsbomb, colour = color_b, 77 | fill = fill_b) + 78 | theme_pitch() + 79 | theme(panel.background = element_rect(fill = fill_b)) 80 | 81 | plot <- plot + 82 | geom_segment(aes(x = x, y = y, 83 | xend = finalX, yend = finalY, color = "red"), 84 | lineend = "round", size = 1.5, arrow = arrow(length = unit(0.10, "inches")), 85 | stat = "identity", position = "identity") + 86 | labs(color = "Outcome of Pass", 87 | caption = plotCaption) + 88 | theme(plot.caption = element_text(color = "black")) 89 | 90 | return(plot) 91 | 92 | } else if (data_type == "statsbomb") { ## STATSBOMB ---- 93 | 94 | if (nrow(data) <= 0 || 95 | sum(x = c("x", "y", "finalX", "finalY") %in% names(data)) != 4) { 96 | stop("The dataset has insufficient columns and/or insufficient data.") 97 | } 98 | 99 | if (outcome == "suc") { 100 | data <- data %>% 101 | filter(is.na(pass.outcome.name)) 102 | } else if (outcome == "unsuc") { 103 | data <- data %>% 104 | filter(!is.na(pass.outcome.name)) 105 | } 106 | 107 | data$pass.outcome.name <- tidyr::replace_na(data$pass.outcome.name, "Successful") 108 | data <- data %>% mutate(colorOutcome = ifelse(pass.outcome.name == "Successful", 109 | "Successful", 110 | "Unsuccessful")) 111 | 112 | if (progressive_pass == TRUE) { 113 | data <- data %>% 114 | mutate(start = sqrt((100 - x)^2 + (50 - y)^2)) %>% 115 | mutate(end = sqrt((100 - finalX)^2 + (50 - finalY)^2)) %>% 116 | mutate(isProg = ifelse(end <= 0.75 * start, 117 | 1, 118 | 0)) 119 | 120 | data <- data %>% filter(isProg == 1) 121 | } 122 | 123 | if (cross == TRUE) { 124 | data <- data %>% 125 | filter(pass.cross == TRUE) 126 | } 127 | 128 | if (shot == TRUE) { 129 | data <- data %>% 130 | filter(pass.shot_assist == TRUE) 131 | } 132 | 133 | if (switch == TRUE) { 134 | data <- data %>% 135 | mutate(delta_y = abs( 136 | finalY - y 137 | )) %>% 138 | filter(delta_y >= 35) 139 | } 140 | 141 | if (progressive_pass == TRUE) { 142 | data <- data %>% 143 | mutate(start = sqrt((120 - x)^2 + (40 - y)^2)) %>% 144 | mutate(end = sqrt((120 - finalX)^2 + (40 - finalY)^2)) %>% 145 | mutate(isProg = ifelse(end <= 0.75 * start, 146 | 1, 147 | 0)) 148 | data <- data %>% filter(isProg == 1) 149 | 150 | } 151 | 152 | ### PLOT STATSBOMB ---- 153 | plot <- ggplot(data = data) + 154 | annotate_pitch(dimensions = pitch_statsbomb, colour = color_b, 155 | fill = fill_b) + 156 | theme_pitch() + 157 | theme(panel.background = element_rect(fill = fill_b)) 158 | 159 | if (nrow(data) > 0) { 160 | if (type == "sep") { 161 | plot <- plot + 162 | geom_segment(aes(x = x, y = y, 163 | xend = finalX, yend = finalY, color = colorOutcome), 164 | lineend = "round", size = 1.5, arrow = arrow(length = unit(0.10, "inches")), 165 | stat = "identity", position = "identity") + 166 | facet_grid(~colorOutcome) + 167 | labs( 168 | color = "Outcome of Pass", 169 | caption = plotCaption 170 | ) 171 | } else if (type == "all") { 172 | plot <- plot + 173 | geom_segment(aes(x = x, y = y, 174 | xend = finalX, yend = finalY, color = colorOutcome), 175 | lineend = "round", size = 1.5, arrow = arrow(length = unit(0.10, "inches")), 176 | stat = "identity", position = "identity") + 177 | labs( 178 | color = "Outcome of Pass", 179 | caption = plotCaption 180 | ) 181 | } 182 | } 183 | return(plot) 184 | } else { 185 | stop("Please input either 'statsbomb' OR 'opta' into the `data_type` argument.") 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /R/plot_passflow.R: -------------------------------------------------------------------------------- 1 | #' Passflow plot function 2 | #' 3 | #' This function takes in a dataframe and binsizes 4 | #' to make a passflow map. Compatible, for right now, with StatsBomb data only. Returns a ggplot object 5 | #' 6 | #' @param data Dataframe that must house pass data only and must contain atleast the following columns: `x`, `y`, `finalX`, `finalY` 7 | #' @param data_type Type of data that is being put in: opta or statsbomb. Default set to "statsbomb" 8 | #' @param binwidth Details the bin size the passflow needs to bin to. The same argument name as the underlying call to `geom_bin2d()`. Default is 20. 9 | #' @return returns a ggplot2 object 10 | #' 11 | #' @importFrom magrittr %>% 12 | #' @import dplyr 13 | #' @importFrom ggplot2 ggplot geom_bin2d geom_segment labs arrow aes scale_fill_gradientn 14 | #' @import ggsoccer 15 | #' @export 16 | #' @examples 17 | #' \dontrun{ 18 | #' plot <- plot_passflow(data, binwidth = 30) 19 | #' plot 20 | #' } 21 | 22 | plot_passflow <- function(data, data_type = "statsbomb", binwidth = 0) { 23 | 24 | fill_b <- "#0d1117" 25 | color_b <- "white" 26 | bin_alpha <- 0.6 27 | 28 | if (binwidth == 0) { 29 | bin <- 20 30 | } else { 31 | bin <- binwidth 32 | } 33 | 34 | x_bin <- 120 / bin 35 | y_bin <- 80 / bin 36 | 37 | passfx <- seq(0, 120, by = bin) 38 | passfy <- seq(0, 80, by = bin) 39 | 40 | if ((nrow(data) > 0) && 41 | sum(x = c("x", "y", "finalX", "finalY") %in% names(data)) == 4) { 42 | 43 | # Converting Opta data 44 | if (data_type == "opta") { 45 | to_sb <- rescale_coordinates(from = pitch_opta, to = pitch_statsbomb) 46 | data$x <- to_sb$x(data$x) 47 | data$y <- to_sb$y(data$y) 48 | data$finalX <- to_sb$x(data$finalX) 49 | data$finalY <- to_sb$y(data$finalY) 50 | } 51 | 52 | PassFlow <- data.frame("x" = 0.0, "y" = 0.0, "finalX" = 0.0, "finalY" = 0.0, countPasses = 0.0) 53 | 54 | for (i in 1:x_bin) { 55 | 56 | filterx <- data %>% 57 | filter(x >= passfx[i]) %>% 58 | filter(x < passfx[i + 1]) 59 | 60 | for (j in 1:y_bin) { 61 | 62 | minY <- passfy[j] 63 | maxY <- passfy[j + 1] 64 | 65 | filtery <- filterx %>% 66 | filter(y >= minY) %>% 67 | filter(y < maxY) 68 | 69 | if (nrow(filtery) >= 1) { 70 | 71 | me_x <- mean(filtery$x) 72 | me_y <- mean(filtery$y) 73 | me_ex <- mean(filtery$finalX) 74 | me_ey <- mean(filtery$finalY) 75 | 76 | count <- nrow(filtery) 77 | 78 | x <- c(me_x, me_y, me_ex, me_ey, count) 79 | PassFlow <- rbind(PassFlow, x) 80 | 81 | } 82 | 83 | } 84 | 85 | } 86 | 87 | PassFlow <- PassFlow[2:nrow(PassFlow), ] 88 | 89 | plot <- PassFlow %>% 90 | ggplot() + 91 | annotate_pitch(dimensions = pitch_statsbomb, colour = color_b, 92 | fill = fill_b) + 93 | theme_pitch() 94 | 95 | plotCaption <- "Created using ggshakeR" 96 | 97 | if (nrow(PassFlow) > 0) { 98 | plot <- plot + 99 | geom_bin2d(data = data, aes(x = x, y = y), alpha = bin_alpha, 100 | binwidth = c(bin, bin), position = "identity") + 101 | scale_fill_gradientn(colours = viridis_d_pal) + 102 | geom_segment(aes(x = x, y = y, xend = finalX, yend = finalY, alpha = countPasses), 103 | color = "white", lineend = "round", size = 2, arrow = arrow(length = unit(0.08, "inches"))) + 104 | labs( 105 | fill = "Count of Passes Started", 106 | alpha = "Number of Passes Made", 107 | caption = plotCaption 108 | ) 109 | } 110 | plot 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /R/plot_scatter.R: -------------------------------------------------------------------------------- 1 | #' Plotting scatter plots 2 | #' 3 | #' This function allows you to plot various types of plots 4 | #' that have passes as some sort of input. Compatible with any data frame of any data type. Returns a ggplot object. 5 | #' 6 | #' 7 | #' @param data the dataframe passed in for plotting. 8 | #' @param x name of column name in data to be used on x-axis 9 | #' @param y name of column name in data to be used on y-axis 10 | #' @param label the name of column name in data to label the scatter plot 11 | #' @param set_size_num sets the size of the points set as a constant. Default size = 5. 12 | #' @param set_size_var Enter name of column name in data to set size based on variable. 13 | #' @param set_color_num sets the color of the points set as a constant. Can enter hexcode or a valid ggplot2 color. Default = "red" 14 | #' @param set_color_var Enter name of column name in data to set color based on variable. 15 | #' @param title pick the title of the scatter plot 16 | #' @param title_size sets the size of the title of the scatter plot. Default size = 25. 17 | #' @param subtitle pick the subtitle of the scatter plot 18 | #' @param subtitle_size sets the size of the subtitle of the scatter plot Default size = 15. 19 | #' @param caption pick the caption of the scatter plot 20 | #' @param caption_size sets the size of the caption of the scatter plot. Default size = 10. 21 | #' @param theme decide the theme of the plot between four choices: classic, minimal, grey, bw. Default = "classic" 22 | #' @return returns a ggplot2 object 23 | #' 24 | #' @importFrom magrittr %>% 25 | #' @import dplyr 26 | #' @import ggplot2 27 | #' @import ggsoccer 28 | #' @import ggrepel 29 | #' 30 | #' @export 31 | #' 32 | #' @examples 33 | #' \dontrun{ 34 | #' plot <- plot_scatter(data, x = "player", y = "age", label = "team") 35 | #' plot 36 | #' } 37 | 38 | plot_scatter <- function(data, x = "", y = "", label = "", 39 | set_size_num = 5, set_size_var = "", 40 | set_color_num = "red", set_color_var = "", 41 | title = "", title_size = 25, 42 | subtitle = "", subtitle_size = 15, 43 | caption = "", caption_size = 10, 44 | theme = "classic") { 45 | if (nrow(data) == 0) { 46 | stop("Please input a data.frame into the 'data' argument.") 47 | } 48 | 49 | ## Pre-processing ---- 50 | total <- 0 51 | scatter_x <- x 52 | scatter_y <- y 53 | scatter_label <- label 54 | 55 | if (scatter_x %in% names(data) && scatter_y %in% names(data)) { 56 | selection <- c(scatter_x, scatter_y) 57 | 58 | renaming <- c('scatter_x', 'scatter_y') 59 | 60 | total <- 2 61 | 62 | col_var <- "" 63 | size_var <- "" 64 | 65 | if (scatter_label != "" && scatter_label %in% names(data)) { 66 | if (scatter_label %in% selection == TRUE) { 67 | data[, "scatter_label"] <- data[, scatter_label] 68 | 69 | selection <- append(selection, 'scatter_label') 70 | renaming <- append(renaming, 'scatter_label') 71 | } else { 72 | selection <- append(selection, scatter_label) 73 | renaming <- append(renaming, 'scatter_label') 74 | } 75 | total <- total + 1 76 | } 77 | 78 | if (set_size_var != "" && set_size_var %in% names(data)) { 79 | if (set_size_var %in% selection == TRUE) { 80 | data[, "set_size_var"] <- data[, set_size_var] 81 | 82 | selection <- append(selection, 'set_size_var') 83 | renaming <- append(renaming, 'set_size_var') 84 | } else { 85 | selection <- append(selection, set_size_var) 86 | renaming <- append(renaming, 'set_size_var') 87 | } 88 | total <- total + 1 89 | 90 | size_var <- set_size_var 91 | } 92 | 93 | if (set_color_var != "" && set_color_var %in% names(data)) { 94 | if (set_color_var %in% selection == TRUE) { 95 | data[, "set_color_var"] <- data[, set_color_var] 96 | 97 | selection <- append(selection, 'set_color_var') 98 | renaming <- append(renaming, 'set_color_var') 99 | } else { 100 | selection <- append(selection, set_color_var) 101 | renaming <- append(renaming, 'set_color_var') 102 | } 103 | total <- total + 1 104 | 105 | col_var <- set_color_var 106 | } 107 | 108 | data <- data %>% 109 | select(c(selection)) 110 | 111 | 112 | for (i in 1:total) { 113 | names(data)[i] <- renaming[i] 114 | } 115 | #Preprocessing over 116 | 117 | # Plotting ---- 118 | plot <- data %>% 119 | ggplot(aes(x = scatter_x, y = scatter_y)) 120 | 121 | x_title <- scatter_x 122 | y_title <- scatter_y 123 | 124 | if (set_size_var != "" && set_color_var != "") { 125 | plot <- plot + 126 | geom_point(aes(size = set_size_var, color = set_color_var)) 127 | } else if (set_size_var == "" && set_color_var != "") { 128 | plot <- plot + 129 | geom_point(aes(color = set_color_var), size = set_size_num) 130 | } else if (set_size_var != "" && set_color_var == "") { 131 | plot <- plot + 132 | geom_point(aes(size = set_size_var), color = set_color_num) 133 | } else if (set_size_var == "" && set_color_var == "") { 134 | plot <- plot + 135 | geom_point(size = set_size_num, color = set_color_num) 136 | } 137 | 138 | if (scatter_label != "") { 139 | plot <- plot + 140 | geom_label_repel(aes(x = scatter_x, y = scatter_y, label = scatter_label), max.overlaps = 2) 141 | } 142 | 143 | ## Theme ---- 144 | if (theme == "classic") { 145 | plot <- plot + 146 | theme_classic() 147 | } else if (theme == "minimal") { 148 | plot <- plot + 149 | theme_minimal() 150 | } else if (theme == "grey") { 151 | plot <- plot + 152 | theme_grey() 153 | } else if (theme == "bw") { 154 | plot <- plot + 155 | theme_bw() 156 | } 157 | 158 | plotCaption <- "Created using ggshakeR" 159 | plotCaption <- paste(caption, plotCaption, sep = "\n") 160 | 161 | 162 | plot <- plot + 163 | labs( 164 | title = title, 165 | subtitle = subtitle, 166 | caption = plotCaption, 167 | color = col_var, 168 | size = size_var, 169 | x = x_title, 170 | y = y_title 171 | ) + 172 | theme( 173 | plot.title = element_text(color = "black", size = title_size), 174 | plot.subtitle = element_text(color = "black", size = subtitle_size), 175 | plot.caption = element_text(color = "black", size = caption_size) 176 | ) 177 | } else { 178 | stop("Please input columns for the 'x' and/or 'y' arguments.") 179 | } 180 | 181 | return(plot) 182 | } 183 | -------------------------------------------------------------------------------- /R/plot_sonar.R: -------------------------------------------------------------------------------- 1 | #' Function for plotting pass sonars 2 | #' 3 | #' This function allows for data, that can be from Opta or Statsbomb, to be used 4 | #' for plotting pass sonars. 5 | #' 6 | #' @param data Dataframe that houses pass data. Dataframe must contain atleast the following columns: `x`, `y`, `finalX`, `finalY` 7 | #' @param data_type Type of data that is being put in: opta or statsbomb. Default set to "statsbomb" 8 | #' @param title Title of the passing sonar plot 9 | #' @return a ggplot2 object 10 | #' 11 | #' @importFrom magrittr %>% 12 | #' @import dplyr 13 | #' @import ggplot2 14 | #' @import ggsoccer 15 | #' 16 | #' @export 17 | #' 18 | #' @examples 19 | #' \dontrun{ 20 | #' plot <- plot_sonar(data, data_type = "statsbomb") 21 | #' plot 22 | #' } 23 | 24 | plot_sonar <- function(data, data_type = "statsbomb", title = "") { 25 | #Prerequiste checking: if the data has rows, whether it has the right columns 26 | #and whether it has the right data_types 27 | if ((nrow(data) > 0) && 28 | sum(c("x", "y", "finalX", "finalY") %in% names(data)) == 4 && 29 | (data_type == "statsbomb" || data_type == "opta")) { 30 | 31 | #Converting opta data to stasbomb data 32 | if (data_type == "opta") { 33 | to_sb <- rescale_coordinates(from = pitch_opta, to = pitch_statsbomb) 34 | data$x <- to_sb$x(data$x) 35 | data$y <- to_sb$y(data$y) 36 | data$finalX <- to_sb$x(data$finalX) 37 | data$finalY <- to_sb$y(data$finalY) 38 | } 39 | 40 | #Adding columns to calculate the angle and other important variables 41 | passData <- data %>% 42 | mutate(xstart = (finalX - x)) %>% 43 | mutate(ystart = (finalY - y)) %>% 44 | mutate(slope = ystart / xstart) %>% 45 | mutate(pass.angle = atan(slope)) %>% 46 | mutate(starty = ystart * ystart) %>% 47 | mutate(startx = xstart * xstart) %>% 48 | mutate(length = startx + starty) %>% 49 | mutate(pass.length = sqrt(length)) 50 | 51 | #default angle that we want to bin by 52 | round.angle <- 30 53 | 54 | #Getting the angles of all the passes 55 | data2 <- passData %>% 56 | mutate(angle.round = round(pass.angle * 360 / pi / round.angle) * round.angle) 57 | 58 | #Summarizing and binning the passes by frequency, distance, and angle 59 | sonar <- data2 %>% 60 | mutate(N = n()) %>% 61 | ungroup() %>% 62 | group_by(angle.round) %>% 63 | mutate(n.angle = n() / N) %>% 64 | ungroup() %>% 65 | mutate(maxN = max(n.angle), 66 | angle.norm = n.angle / maxN) %>% 67 | ungroup() %>% 68 | group_by(angle.round, N, n.angle) %>% 69 | summarize(angle.norm = mean(angle.norm), 70 | frequency = mean(n.angle), 71 | distance = mean(pass.length), 72 | distance = ifelse(distance > 30, 30, distance)) 73 | 74 | #Adding an offset for cleaner aesthetics 75 | sonar <- sonar %>% 76 | mutate(angle.round = angle.round + 0.5) 77 | 78 | if (title == "") { 79 | title <- "Pass Sonar" 80 | } 81 | 82 | plotCaption <- paste("Length of passes is in length of arrows + color of dots while frequency is in transparency. ", "Forward is toward's opponent's goal while backwards is towards own goal. ", sep = "\n") 83 | plotCaption <- paste(plotCaption, "Left and right correspond to left and right of pitch. Created using ggshakeR", sep = "\n") 84 | #drawing the sonar plot 85 | plot <- ggplot(data = sonar) + 86 | geom_segment(aes(x = angle.round, y = 0, xend = angle.round, yend = angle.norm, alpha = frequency), 87 | size = 2, 88 | color = "white", 89 | lineend = "round", 90 | arrow = arrow(length = unit(0.03, "npc"))) + 91 | scale_alpha(guide = 'none') + 92 | geom_point(aes(x = angle.round, y = 0, color = distance), size = 6) + 93 | scale_color_gradientn(colours = zissou_pal) + 94 | scale_x_continuous(breaks = seq(-180, 180, by = 90), limits = c(-180, 180)) + 95 | coord_polar(start = pi, direction = -1) + 96 | labs(x = '', y = '', title = title, 97 | alpha = "Frequency of Passes", color = "Avg. Distance", 98 | caption = plotCaption) + 99 | ylim(-0.5, 1.5) + 100 | theme(plot.title = element_text(hjust = 0.5, color = "white", size = 25), 101 | plot.caption = element_text(color = "white"), 102 | plot.background = element_rect(fill = "black", color = NA), 103 | panel.background = element_rect(fill = "black", color = NA), 104 | panel.grid.major = element_blank(), 105 | panel.grid.minor = element_blank(), 106 | panel.border = element_blank(), 107 | axis.text.x = element_blank(), 108 | axis.text.y = element_blank()) 109 | 110 | return(plot) 111 | } else { 112 | stop("Dataframe has insufficient number of rows and/or you don't have the right amount of columns: `x`, `y`, `finalX`, `finalY`") 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /R/plot_timeline.r: -------------------------------------------------------------------------------- 1 | #' Create timeline charts (xG) using 'understat' event data. 2 | #' 3 | #'This function allows to make match timelines using data collected event-by-event. 4 | #' 5 | #' @param data Data, for now only compatible with understat data. 6 | #' @param match_year the year the match was played. 7 | #' @param team_home home team according to data. 8 | #' @param team_away away team according to data. 9 | #' @param color_home line colour for the home team. 10 | #' @param color_away line colour for the away team. 11 | #' @param theme Choose from 4 ggplot2 themes -> dark, almond, rose, white. 12 | #' @return ggplot2 object of a xG timeline plot 13 | #' 14 | #' @import dplyr 15 | #' @import ggtext 16 | #' @import ggplot2 17 | #' @import ggrepel 18 | #' 19 | #' @export 20 | #' 21 | #' @examples 22 | #' \dontrun{ 23 | #' plot <- plot_timeline(data = data, match_year = 2021, 24 | #' team_home = "Manchester United", team_away = "Liverpool", 25 | #' color_home = "#e31a1c", color_away = "#980043", theme = "dark") 26 | #' plot 27 | #' } 28 | 29 | plot_timeline <- function(data, match_year, 30 | team_home, team_away, 31 | color_home, color_away, 32 | theme = "") { 33 | 34 | if (!"season" %in% colnames(data)) { 35 | data <- data %>% 36 | rename(season = year) 37 | } 38 | 39 | if (!"home_away" %in% colnames(data)) { 40 | data <- data %>% 41 | rename(home_away = h_a) 42 | } 43 | 44 | data <- data %>% 45 | dplyr::filter(season == match_year) %>% 46 | ## Prepare goal labels 47 | mutate(player_label = dplyr::case_when( 48 | result == "Goal" & situation != "Penalty" ~ paste0(player, ": ", round(xG, digits = 2), " xG"), 49 | result == "Goal" & situation == "Penalty" ~ paste0(player, " (Penalty): ", round(xG, digits = 2), " xG"), 50 | result == "OwnGoal" ~ paste0(player, " (Own Goal): ", round(xG, digits = 2), " xG"), 51 | TRUE ~ ""), 52 | minute = as.numeric(minute)) %>% 53 | dplyr::filter(home_team == team_home, 54 | away_team == team_away) %>% 55 | ## Calculate xG cumulative sum per team 56 | group_by(home_away) %>% 57 | mutate(xGsum = cumsum(xG)) %>% 58 | ungroup() 59 | 60 | data1 <- data %>% 61 | dplyr::filter(home_away == "h") %>% 62 | mutate(xGsum = cumsum(xG)) 63 | data2 <- data %>% 64 | dplyr::filter(home_away == "a") %>% 65 | mutate(xGsum = cumsum(xG)) 66 | 67 | # data1 <- insertRows(data1, 1, new = 0) 68 | # data2 <- insertRows(data2, 1, new = 0) 69 | data1 <- dplyr::add_row(.data = data1, .before = 1, 70 | id = "0", minute = 0, result = "0", X = 0, Y = 0, xG = 0, player = "0", home_away = "0", player_id = "0", 71 | situation = "0", season = "0", shotType = "0", match_id = "0", home_team = "0", away_team = "0", 72 | home_goals = 0, away_goals = 0, date = "0", player_assisted = "0", lastAction = "0", xGsum = 0) 73 | 74 | data2 <- dplyr::add_row(.data = data2, .before = 1, 75 | id = "0", minute = 0, result = "0", X = 0, Y = 0, xG = 0, player = "0", home_away = "0", player_id = "0", 76 | situation = "0", season = "0", shotType = "0", match_id = "0", home_team = "0", away_team = "0", 77 | home_goals = 0, away_goals = 0, date = "0", player_assisted = "0", lastAction = "0", xGsum = 0) 78 | 79 | dat1 <- data1 %>% 80 | dplyr::filter(result == "Goal") 81 | d1 <- data1 %>% 82 | dplyr::filter(result == "OwnGoal") 83 | dat1 <- rbind(dat1, d1) 84 | dat2 <- data2 %>% 85 | dplyr::filter(result == "Goal") 86 | d2 <- data2 %>% 87 | dplyr::filter(result == "OwnGoal") 88 | dat2 <- rbind(dat2, d2) 89 | 90 | ## Set up labels for plot title ---- 91 | team1 <- unique(data$home_team) 92 | team2 <- unique(data$away_team) 93 | 94 | xG1 <- round(sum(data1$xG), 2) 95 | xG2 <- round(sum(data2$xG), 2) 96 | 97 | g1 <- unique(data$home_goals) 98 | g2 <- unique(data$away_goals) 99 | 100 | if (any(g1 == 1)) { 101 | gls1 <- "Goal" 102 | } else { 103 | gls1 <- "Goals" 104 | } 105 | 106 | if (any(g2 == 1)) { 107 | gls2 <- "Goal" 108 | } else { 109 | gls2 <- "Goals" 110 | } 111 | 112 | ## Plot title ---- 113 | # plot_title <- glue(" {team1} : {g1} {gls1} ({xG1} xG) vs. {team2} : {g2} {gls2} ({xG2} xG)") 114 | plot_title <- sprintf(" %s : %s %s (%s xG) vs. %s : %s %s (%s xG)", 115 | color_home, team1, g1, gls1, xG1, 116 | color_away, team2, g2, gls2, xG2) 117 | 118 | ## Plot themes ---- 119 | fill_b <- "" 120 | colour_b <- "" 121 | colorLine <- "" 122 | colorText <- "" 123 | gridc <- "" 124 | 125 | if (theme == "dark" || theme == "") { 126 | fill_b <- "#0d1117" 127 | color_b <- "white" 128 | 129 | colorLine <- "white" 130 | gridc <- "#525252" 131 | colorText <- "white" 132 | } else if (theme == "white") { 133 | fill_b <- "#F5F5F5" 134 | color_b <- "black" 135 | 136 | colorLine <- "black" 137 | gridc <- "grey" 138 | colorText <- "black" 139 | } else if (theme == "rose") { 140 | fill_b <- "#FFE4E1" 141 | color_b <- "#696969" 142 | 143 | colorLine <- "#322E2E" 144 | gridc <- "grey" 145 | colorText <- "#322E2E" 146 | } else if (theme == "almond") { 147 | fill_b <- "#FFEBCD" 148 | color_b <- "#696969" 149 | 150 | colorLine <- "#322E2E" 151 | gridc <- "grey" 152 | colorText <- "#322E2E" 153 | } 154 | 155 | plotCaption <- "Created using ggshakeR" 156 | 157 | ## Plot! ---- 158 | plot_timeline <- ggplot() + 159 | geom_step(data = data1, aes(x = minute, y = xGsum), color = color_home, size = 3) + 160 | geom_step(data = data2, aes(x = minute, y = xGsum), color = color_away, size = 3) + 161 | geom_point(data = dat1, aes(x = minute, y = xGsum), color = color_home, fill = fill_b, shape = 21, stroke = 2, size = 6) + 162 | geom_point(data = dat2, aes(x = minute, y = xGsum), color = color_away, fill = fill_b, shape = 21, stroke = 2, size = 6) + 163 | geom_text_repel(data = data %>% filter(result %in% c("Goal", "OwnGoal")), 164 | aes(x = minute, y = xGsum, label = player_label), 165 | box.padding = 2, 166 | point.padding = 1.5, 167 | segment.color = colorLine, 168 | alpha = 0.8, 169 | color = colorText, 170 | size = 5) + 171 | theme_minimal() + 172 | labs(title = plot_title, caption = plotCaption) + 173 | theme(plot.title = element_markdown(lineheight = 1.1, color = colorText, hjust = 0.5, size = 20, face = "bold")) + 174 | theme(plot.background = element_rect(fill = fill_b, color = color_b)) + 175 | theme(panel.background = element_rect(fill = fill_b, color = color_b)) + 176 | labs(x = "Minute", y = "Cumulative xG") + 177 | theme(axis.title.x = element_text(color = colorLine, size = 15, face = "bold")) + 178 | theme(axis.title.y = element_text(color = colorLine, size = 15, face = "bold")) + 179 | theme(axis.text.x = element_text(color = colorLine, size = 12), 180 | axis.text.y = element_text(color = colorLine, size = 12)) + 181 | theme(panel.grid.major = element_line(color = gridc, size = 0.5, linetype = "dashed")) + 182 | theme(panel.grid.minor = element_blank()) + 183 | theme(axis.line = element_blank(), 184 | panel.border = element_blank(), 185 | panel.background = element_blank()) + 186 | scale_x_continuous(breaks = c(0, 10, 20, 30, 40, 50, 60, 70, 80, 90)) + 187 | geom_vline(xintercept = 45, linetype = "dashed", color = colorLine, size = 1) 188 | 189 | return(plot_timeline) 190 | } 191 | -------------------------------------------------------------------------------- /R/plot_trendline.R: -------------------------------------------------------------------------------- 1 | #' Function for plotting xG Trendline with FBref/ StatsBomb data. 2 | #' 3 | #' The data can be scraped from FBref.\cr 4 | #' Dataframe passed in must have the following column names: \cr 5 | #' \cr 6 | #' Date (format: year-month-day).yyyy-mm-dd, \cr 7 | #' Home_xG (xG for Home Team), \cr 8 | #' Away_xG (xG for Away Team), \cr 9 | #' Home (Home Team), \cr 10 | #' Away (Away Team) 11 | #' 12 | #' For best clarity, export plot as a 2000x1000 png 13 | #' 14 | #' @param data is for the dataset used. Select the number of matches wanted in the viz beforehand. 15 | #' @param team is to select the specific team for the viz. Team must be accurate as per FBref specifications. 16 | #' @param color_xg is for selecting color for xGoals. 17 | #' @param color_xga is for selecting the color for xGoalsAgainst. 18 | #' @param rolling_average is for setting the rolling average for the data. 19 | #' @param theme to select the theme from 4 options -> dark, almond, rose, white. 20 | #' 21 | #' @import dplyr 22 | #' @import ggplot2 23 | #' @import ggtext 24 | #' @import TTR 25 | #' 26 | #' @export 27 | #' 28 | #' @examples 29 | #' \dontrun{ 30 | #' plot <- plot_trendline(data = pl, team = "Tottenham", 31 | #' color_xg = "#08519c", color_xga = "#cb181d", 32 | #' rolling_average = 10, theme = "dark") 33 | #' plot 34 | #' } 35 | 36 | plot_trendline <- function(data, team, color_xg, color_xga, rolling_average, theme = "") { 37 | 38 | ## Clean data ---- 39 | data <- data[complete.cases(data[, 'Date']), ] 40 | data <- data[complete.cases(data[, 'Home_xG']), ] 41 | data <- data[complete.cases(data[, 'Away_xG']), ] 42 | 43 | df1 <- data %>% 44 | filter(Home == team) 45 | df1 <- df1[, c("Date", "Home", "Home_xG")] 46 | df1 <- df1 %>% 47 | rename(xG = Home_xG) %>% 48 | rename(Team = Home) 49 | df2 <- data %>% 50 | filter(Away == team) 51 | df2 <- df2[, c("Date", "Away", "Away_xG")] 52 | df2 <- df2 %>% 53 | rename(xG = Away_xG) %>% 54 | rename(Team = Away) 55 | df <- rbind(df1, df2) 56 | 57 | df3 <- data %>% 58 | filter(Home == team) 59 | df3 <- df3[, c("Date", "Away", "Away_xG")] 60 | df3 <- df3 %>% 61 | rename(xGA = Away_xG) %>% 62 | rename(Team = Away) 63 | df4 <- data %>% 64 | filter(Away == team) 65 | df4 <- df4[, c("Date", "Home", "Home_xG")] 66 | df4 <- df4 %>% 67 | rename(xGA = Home_xG) %>% 68 | rename(Team = Home) 69 | dfa <- rbind(df3, df4) 70 | dfa <- dfa[, 3] 71 | data <- cbind(df, dfa) 72 | data <- data %>% 73 | rename(xGA = dfa) %>% 74 | mutate(xGSUM = (xG + xGA) / 2) 75 | 76 | data <- data[order(as.Date(data$Date), decreasing = FALSE), ] 77 | 78 | if (nrow(data) > 0) { 79 | data <- data %>% 80 | mutate(xGSM = TTR::SMA(xG, n = rolling_average), 81 | xGASM = TTR::SMA(xGA, n = rolling_average), 82 | xGSUM = TTR::SMA(xGSUM, n = rolling_average)) 83 | } 84 | 85 | ## Plot titles 86 | team <- paste(team, "xG Trendline") 87 | # subtitle <- glue("{rolling_average} Game Rolling Average [ xG vs xGA ]") 88 | subtitle <- sprintf("%s Game Rolling Average [ xG vs xGA ]", 89 | rolling_average, color_xg, color_xga) 90 | 91 | ## Plot themes 92 | fill_b <- "" 93 | color_b <- "" 94 | colorLine <- "" 95 | colorText <- "" 96 | gridc <- "" 97 | 98 | if (theme == "dark" || theme == "") { 99 | fill_b <- "#0d1117" 100 | color_b <- "white" 101 | 102 | colorLine <- "white" 103 | gridc <- "#525252" 104 | colorText <- "white" 105 | } else if (theme == "white") { 106 | fill_b <- "#F5F5F5" 107 | color_b <- "black" 108 | 109 | colorLine <- "black" 110 | gridc <- "grey" 111 | colorText <- "black" 112 | } else if (theme == "rose") { 113 | fill_b <- "#FFE4E1" 114 | color_b <- "#696969" 115 | 116 | colorLine <- "#322E2E" 117 | gridc <- "grey" 118 | colorText <- "#322E2E" 119 | } else if (theme == "almond") { 120 | fill_b <- "#FFEBCD" 121 | color_b <- "#696969" 122 | 123 | colorLine <- "#322E2E" 124 | gridc <- "grey" 125 | colorText <- "#322E2E" 126 | } 127 | 128 | plotCaption <- "Created using ggshakeR" 129 | 130 | ## PLOT! ---- 131 | trendline_plot <- ggplot(data, aes(x = Date)) + 132 | ## Lines 133 | geom_line(aes(y = xGSM), color = color_xg, size = 3) + 134 | geom_line(aes(y = xGASM), color = color_xga, size = 3) + 135 | geom_line(aes(y = xGSUM), color = fill_b, size = 0.1) + 136 | ## Points 137 | geom_point(aes(y = xGSM), color = color_xg, size = 4) + 138 | geom_point(aes(y = xGASM), color = color_xga, size = 4) + 139 | scale_color_manual(values = c(color_xg, color_xga)) + 140 | expand_limits(y = c(0.25, 2.25)) + 141 | ## labels 142 | labs(title = team, 143 | subtitle = subtitle, 144 | x = "Year", y = "xG", 145 | caption = plotCaption) + 146 | ## theme 147 | theme(plot.title = element_markdown(lineheight = 1.1, size = 40, color = colorText, face = "bold"), 148 | plot.subtitle = element_textbox_simple(lineheight = 1.1, size = 30, color = colorText), 149 | plot.background = element_rect(fill = fill_b, color = color_b), 150 | # panel.background = element_rect(fill = fill_b, color = color_b), 151 | axis.title.x = element_text(color = colorText, size = 24, face = "bold"), 152 | axis.title.y = element_text(color = colorText, size = 24, face = "bold"), 153 | axis.text.x = element_text(color = colorText, size = 18), 154 | axis.text.y = element_text(color = colorText, size = 18), 155 | panel.grid.major = element_line(color = gridc, size = 1, linetype = "dashed"), 156 | panel.grid.minor = element_blank(), 157 | panel.grid.major.x = element_line(color = gridc, size = 1, linetype = "dashed"), 158 | panel.background = element_blank(), 159 | axis.line = element_blank()) + 160 | geom_ribbon(aes(ymin = xGASM, ymax = xGSUM, x = Date), fill = color_xga, alpha = 0.4) + 161 | geom_ribbon(aes(ymin = xGSUM, ymax = xGSM, x = Date), fill = color_xg, alpha = 0.4) + 162 | stat_smooth(method = 'lm', aes(y = xGSM), color = color_xg, linetype = "dashed", alpha = 0.5, size = 2, se = FALSE) + 163 | stat_smooth(method = 'lm', aes(y = xGA), color = color_xga, linetype = "dashed", alpha = 0.5, size = 2, se = FALSE) 164 | 165 | return(trendline_plot) 166 | } 167 | -------------------------------------------------------------------------------- /R/plot_voronoi.R: -------------------------------------------------------------------------------- 1 | #' Function for plotting voronoi diagrams on the pitch 2 | #' 3 | #' The function allows for creation of voronoi diagrams on a football pitch 4 | #' with either Opta or Statsbomb data. 5 | #' 6 | #' @param data Dataframe that houses pass data. Opta/Statsbomb dataframe must contain atleast the following columns: `x`, `y` 7 | #' @param data_type Type of data that is being put in: opta or statsbomb. Default set to "statsbomb". 8 | #' @param colour The colour of the points in the voronoi plot. 9 | #' @param fill Name of column to add a fill component to the plot 10 | #' @param alpha Alpha value for opacity of fill. Default set to 0.4 11 | #' @param title Title of the plot. 12 | #' @param theme Indicates what theme the map must be shown in: dark (default), white, rose, almond. 13 | #' @return a ggplot2 object 14 | #' 15 | #' @import dplyr 16 | #' @import tidyr 17 | #' @import ggplot2 18 | #' @import ggsoccer 19 | #' @importFrom ggforce geom_voronoi_tile geom_voronoi_segment 20 | #' 21 | #' @export 22 | #' 23 | #' @examples 24 | #' \dontrun{ 25 | #' plot <- plot_voronoi(data = data, colour = "blue", title = "Team 1") 26 | #' plot 27 | #' } 28 | #' 29 | plot_voronoi <- function(data, data_type = "statsbomb", 30 | colour = "#E74C3C", 31 | fill = "", 32 | alpha = 0.4, 33 | title = "", 34 | theme = "dark") { 35 | 36 | if (data_type == "opta") { 37 | if (nrow(data) <= 0 || 38 | sum(x = c("x", "y") %in% names(data)) < 2) { 39 | stop("The dataset has insufficient columns and/or insufficient data.") 40 | } 41 | 42 | to_sb <- rescale_coordinates(from = pitch_opta, to = pitch_statsbomb) 43 | data$x <- to_sb$x(data$x) 44 | data$y <- to_sb$y(data$y) 45 | 46 | data <- data %>% 47 | drop_na(x, y) 48 | 49 | } else if (data_type == "statsbomb") { 50 | if (nrow(data) <= 0 || 51 | sum(x = c("x", "y") %in% names(data)) < 2) { 52 | stop("The dataset has insufficient columns and/or insufficient data.") 53 | } 54 | 55 | data <- data %>% drop_na(x, y) 56 | } 57 | 58 | if (fill %in% names(data) == TRUE) { 59 | data[, "fill"] <- data[, fill] 60 | } 61 | 62 | ## Theme ---- 63 | if (theme == "dark") { 64 | fill_b <- "#0d1117" 65 | colour_b <- "white" 66 | } else if (theme == "white") { 67 | fill_b <- "#F5F5F5" 68 | colour_b <- "black" 69 | } else if (theme == "rose") { 70 | fill_b <- "#FFE4E1" 71 | colour_b <- "#696969" 72 | } else if (theme == "almond") { 73 | fill_b <- "#FFEBCD" 74 | colour_b <- "#696969" 75 | } 76 | 77 | ## PLOT ---- 78 | voro_plot <- ggplot(data, aes(x, y)) + 79 | annotate_pitch(dimensions = pitch_statsbomb, colour = colour_b, 80 | fill = fill_b) + 81 | theme_pitch() + 82 | geom_point(color = colour, alpha = 0.7, size = 5) 83 | 84 | if (fill != "") { 85 | voro_plot <- voro_plot + 86 | geom_voronoi_tile(data = data, aes(fill = fill), alpha = alpha, bound = c(0, 120, 0, 80)) 87 | } 88 | 89 | plotCaption <- "Created using ggshakeR" 90 | 91 | voro_plot <- voro_plot + 92 | geom_voronoi_segment(color = "white", bound = c(0, 120, 0, 80)) + 93 | coord_fixed() + 94 | labs(title = title, 95 | x = "Direction of play faces rightward", 96 | fill = fill, 97 | caption = plotCaption) + 98 | theme(plot.background = element_rect(fill = fill_b, colour = NA), 99 | panel.background = element_rect(fill = fill_b, colour = NA), 100 | strip.background = element_rect(fill = fill_b, colour = NA), 101 | strip.text = element_text(colour = colour_b, size = 10), 102 | plot.title = element_text(colour = colour_b, size = 20, hjust = 0.5, face = "bold"), 103 | axis.title.x = element_text(colour = colour_b, size = 12, face = "bold")) 104 | 105 | return(voro_plot) 106 | } 107 | -------------------------------------------------------------------------------- /R/sample_event_data_guide.R: -------------------------------------------------------------------------------- 1 | #' @title Sample event data frame 2 | #' 3 | #' @description Event \code{dataframe} to play with and understand usage of functions 4 | #' 5 | #' @format A \code{dataframe} with the columns `x`, `y`, `finalX`, `finalY`, `minute`, 6 | #' `playerId`, `teamId`, `outcome` and `type`. 7 | #' 8 | "SampleEventData" -------------------------------------------------------------------------------- /R/sample_sbevent_data_guide.R: -------------------------------------------------------------------------------- 1 | #' @title Sample StatsBomb event data frame 2 | #' 3 | #' @description StatsBomb event \code{dataframe} to play with and understand usage of functions. 4 | #' The following code was used with the use of {StatsBombR}: 5 | #' 6 | #' \code{ Comp <- FreeCompetitions() %>% } 7 | #' \code{ filter(competition_id == 11 & season_name == "2014/2015") } 8 | #' \code{ Matches <- FreeMatches(Comp) } 9 | #' \code{ StatsBombData <- StatsBombFreeEvents(MatchesDF = Matches, Parallel = TRUE) } 10 | #' \code{ plotting_data <- allclean(StatsBombData) } 11 | #' \code{ plotting_data <- plotting_data %>% } 12 | #' \code{ rename("x" = "location.x", } 13 | #' \code{ "y" = "location.y", } 14 | #' \code{ "finalX" = "pass.end_location.x", } 15 | #' \code{ "finalY" = "pass.end_location.y") } 16 | 17 | #' @format A \code{dataframe} with the columns `x`, `y`, `finalX`, `finalY`, and the rest of StatsBomb columns 18 | #' 19 | "SampleSBData" 20 | -------------------------------------------------------------------------------- /R/threat_guide.R: -------------------------------------------------------------------------------- 1 | #' @title xT calculation data for analysis 2 | #' 3 | #' @description a simple \code{dataframe} that is used for 4 | #' calculating expected threat values 5 | #' 6 | #' @format A \code{dataframe} with 12 columns and 8 rows spanning 7 | #' a football pitch. 8 | #' 9 | "xTGrid" 10 | -------------------------------------------------------------------------------- /R/utils.R: -------------------------------------------------------------------------------- 1 | #' @title text_wrap 2 | #' @description Wrap text using stringi to mimic stringr 3 | #' @param x text vector 4 | #' @return text vector 5 | #' @rdname text_wrap 6 | #' @keywords internal 7 | #' @importFrom stringi stri_wrap stri_c 8 | 9 | text_wrap <- function(x) { 10 | wrapped_text <- stri_wrap(x, width = 10, whitespace_only = TRUE, simplify = FALSE) 11 | final_text <- vapply(wrapped_text, stri_c, collapse = "\n", character(1)) 12 | 13 | return(final_text) 14 | } 15 | 16 | 17 | #' @title shift_column 18 | #' @description Port of `shift.column()` from Jared Lander's {useful} package 19 | #' with a few minor changes. 20 | #' @param data data 21 | #' @param columns columns to shift over 22 | #' @param new_names New name of shifted columns, Default: sprintf("%s.Shifted", columns) 23 | #' @param len length of rows to shift, Default: 1 24 | #' @param up shift rows up (TRUE) or down (FALSE), Default: TRUE 25 | #' @return data.frame with shifted columns 26 | #' @keywords internal 27 | #' @rdname shift_column 28 | 29 | shift_column <- function(data, columns, new_names = sprintf("%s.Shifted", columns), len = 1L, up = TRUE) { 30 | if (length(columns) != length(new_names)) { 31 | stop("columns and new_names must be the same length") 32 | } 33 | 34 | # get the rows to keep based on how much to shift it by and weather to shift up or down 35 | rowsToKeep <- seq(from = 1 + len * up, length.out = NROW(data) - len) 36 | 37 | # for the original data -- it needs to be shifted the other way 38 | dataRowsToKeep <- seq(from = 1 + len * !up, length.out = NROW(data) - len) 39 | 40 | #create a df of the shifted rows 41 | shiftedDF <- data[rowsToKeep, columns] 42 | 43 | # give the right names to these new columns 44 | names(shiftedDF) <- new_names 45 | 46 | # data names 47 | dataNames <- names(data) 48 | 49 | # get rid of excess rows in data 50 | data <- data[dataRowsToKeep, ] 51 | 52 | # tack shifted data onto the end of the original (and cutoff) data 53 | data <- cbind(data, shiftedDF) 54 | names(data) <- c(dataNames, new_names) 55 | 56 | return(data) 57 | } 58 | 59 | 60 | #' @title hull_fun 61 | #' @description Create boundaries for convex hull 62 | #' @param x data frame 63 | #' @return data frame 64 | #' @rdname hull_fun 65 | #' @keywords internal 66 | #' @importFrom dplyr filter slice 67 | #' @importFrom grDevices chull 68 | #' @importFrom stats quantile 69 | 70 | hull_fun <- function(data) { 71 | 72 | x_low <- quantile(data$x, 0.05) 73 | x_high <- quantile(data$x, 0.95) 74 | 75 | y_low <- quantile(data$y, 0.05) 76 | y_high <- quantile(data$y, 0.95) 77 | 78 | hull_data <- data %>% 79 | filter((x > x_low) & (x < x_high)) %>% 80 | filter((y > y_low) & (y < y_high)) %>% 81 | slice(chull(x, y)) 82 | 83 | return(hull_data) 84 | } 85 | 86 | 87 | #' title zissou_pal 88 | #' @description 'Zissou' palette hex codes. Useed in `plot_sonar()` function. 89 | #' Created by Karthik Ram in the {wesanderson} R package. 90 | #' @keywords internal 91 | 92 | zissou_pal <- c("#3B9AB2", "#56A6BA", "#71B3C2", "#9EBE91", "#D1C74C", 93 | "#E8C520", "#E4B80E", "#E29E00", "#EA5C00", "#F21A00") 94 | 95 | 96 | #' @title viridis_d_pal 97 | #' @description Viridis palette hex codes. Used in `plot_passflow()` function. 98 | #' Created by Stefan van der Walt & Nathaniel Smith for matplotlib. 99 | #' @keywords internal 100 | 101 | viridis_d_pal <- c("#440154FF", "#440256FF", "#450457FF", "#450559FF", "#46075AFF", 102 | "#46085CFF", "#460A5DFF", "#460B5EFF", "#470D60FF", "#470E61FF", 103 | "#471063FF", "#471164FF", "#471365FF", "#481467FF", "#481668FF", 104 | "#481769FF", "#48186AFF", "#481A6CFF", "#481B6DFF", "#481C6EFF", 105 | "#481D6FFF", "#481F70FF", "#482071FF", "#482173FF", "#482374FF", 106 | "#482475FF", "#482576FF", "#482677FF", "#482878FF", "#482979FF", 107 | "#472A7AFF", "#472C7AFF", "#472D7BFF", "#472E7CFF", "#472F7DFF", 108 | "#46307EFF", "#46327EFF", "#46337FFF", "#463480FF", "#453581FF", 109 | "#453781FF", "#453882FF", "#443983FF", "#443A83FF", "#443B84FF", 110 | "#433D84FF", "#433E85FF", "#423F85FF", "#424086FF", "#424186FF", 111 | "#414287FF", "#414487FF", "#404588FF", "#404688FF", "#3F4788FF", 112 | "#3F4889FF", "#3E4989FF", "#3E4A89FF", "#3E4C8AFF", "#3D4D8AFF", 113 | "#3D4E8AFF", "#3C4F8AFF", "#3C508BFF", "#3B518BFF", "#3B528BFF", 114 | "#3A538BFF", "#3A548CFF", "#39558CFF", "#39568CFF", "#38588CFF", 115 | "#38598CFF", "#375A8CFF", "#375B8DFF", "#365C8DFF", "#365D8DFF", 116 | "#355E8DFF", "#355F8DFF", "#34608DFF", "#34618DFF", "#33628DFF", 117 | "#33638DFF", "#32648EFF", "#32658EFF", "#31668EFF", "#31678EFF", 118 | "#31688EFF", "#30698EFF", "#306A8EFF", "#2F6B8EFF", "#2F6C8EFF", 119 | "#2E6D8EFF", "#2E6E8EFF", "#2E6F8EFF", "#2D708EFF", "#2D718EFF", 120 | "#2C718EFF", "#2C728EFF", "#2C738EFF", "#2B748EFF", "#2B758EFF", 121 | "#2A768EFF", "#2A778EFF", "#2A788EFF", "#29798EFF", "#297A8EFF", 122 | "#297B8EFF", "#287C8EFF", "#287D8EFF", "#277E8EFF", "#277F8EFF", 123 | "#27808EFF", "#26818EFF", "#26828EFF", "#26828EFF", "#25838EFF", 124 | "#25848EFF", "#25858EFF", "#24868EFF", "#24878EFF", "#23888EFF", 125 | "#23898EFF", "#238A8DFF", "#228B8DFF", "#228C8DFF", "#228D8DFF", 126 | "#218E8DFF", "#218F8DFF", "#21908DFF", "#21918CFF", "#20928CFF", 127 | "#20928CFF", "#20938CFF", "#1F948CFF", "#1F958BFF", "#1F968BFF", 128 | "#1F978BFF", "#1F988BFF", "#1F998AFF", "#1F9A8AFF", "#1E9B8AFF", 129 | "#1E9C89FF", "#1E9D89FF", "#1F9E89FF", "#1F9F88FF", "#1FA088FF", 130 | "#1FA188FF", "#1FA187FF", "#1FA287FF", "#20A386FF", "#20A486FF", 131 | "#21A585FF", "#21A685FF", "#22A785FF", "#22A884FF", "#23A983FF", 132 | "#24AA83FF", "#25AB82FF", "#25AC82FF", "#26AD81FF", "#27AD81FF", 133 | "#28AE80FF", "#29AF7FFF", "#2AB07FFF", "#2CB17EFF", "#2DB27DFF", 134 | "#2EB37CFF", "#2FB47CFF", "#31B57BFF", "#32B67AFF", "#34B679FF", 135 | "#35B779FF", "#37B878FF", "#38B977FF", "#3ABA76FF", "#3BBB75FF", 136 | "#3DBC74FF", "#3FBC73FF", "#40BD72FF", "#42BE71FF", "#44BF70FF", 137 | "#46C06FFF", "#48C16EFF", "#4AC16DFF", "#4CC26CFF", "#4EC36BFF", 138 | "#50C46AFF", "#52C569FF", "#54C568FF", "#56C667FF", "#58C765FF", 139 | "#5AC864FF", "#5CC863FF", "#5EC962FF", "#60CA60FF", "#63CB5FFF", 140 | "#65CB5EFF", "#67CC5CFF", "#69CD5BFF", "#6CCD5AFF", "#6ECE58FF", 141 | "#70CF57FF", "#73D056FF", "#75D054FF", "#77D153FF", "#7AD151FF", 142 | "#7CD250FF", "#7FD34EFF", "#81D34DFF", "#84D44BFF", "#86D549FF", 143 | "#89D548FF", "#8BD646FF", "#8ED645FF", "#90D743FF", "#93D741FF", 144 | "#95D840FF", "#98D83EFF", "#9BD93CFF", "#9DD93BFF", "#A0DA39FF", 145 | "#A2DA37FF", "#A5DB36FF", "#A8DB34FF", "#AADC32FF", "#ADDC30FF", 146 | "#B0DD2FFF", "#B2DD2DFF", "#B5DE2BFF", "#B8DE29FF", "#BADE28FF", 147 | "#BDDF26FF", "#C0DF25FF", "#C2DF23FF", "#C5E021FF", "#C8E020FF", 148 | "#CAE11FFF", "#CDE11DFF", "#D0E11CFF", "#D2E21BFF", "#D5E21AFF", 149 | "#D8E219FF", "#DAE319FF", "#DDE318FF", "#DFE318FF", "#E2E418FF", 150 | "#E5E419FF", "#E7E419FF", "#EAE51AFF", "#ECE51BFF", "#EFE51CFF", 151 | "#F1E51DFF", "#F4E61EFF", "#F6E620FF", "#F8E621FF", "#FBE723FF", 152 | "#FDE725FF") 153 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | --- 4 | 5 | 6 | 7 | ```{r, include = FALSE} 8 | knitr::opts_chunk$set( 9 | collapse = TRUE, 10 | comment = "#>", 11 | fig.path = "man/figures/README-", 12 | out.width = "100%", 13 | eval = FALSE 14 | ) 15 | ``` 16 | 17 | # ggshakeR 18 | 19 | 20 | [![Lifecycle: experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://lifecycle.r-lib.org/articles/stages.html#experimental) 21 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 22 | [![check-standard](https://github.com/abhiamishra/ggshakeR/workflows/R-CMD-check/badge.svg)](https://github.com/abhiamishra/ggshakeR/actions) 23 | [![codecov](https://codecov.io/gh/abhiamishra/ggshakeR/branch/main/graph/badge.svg?token=GO5918U56P)](https://codecov.io/gh/abhiamishra/ggshakeR) 24 | 25 | 26 | Welcome to `ggshakeR`! 27 | 28 | This is R's all-inclusive package for visualization and analysis of publicly available soccer data. 29 | 30 | This package aims to provide people interested in football analysis and visualization a platform to learn about it while also learning a new language at the same time. This package is designed to work with free soccer data and, for now, include: 31 | 32 | * [FBref](https://fbref.com/en/) 33 | * [StatsBomb Open Dataset](https://github.com/statsbomb/StatsBombR) 34 | * [Understat](https://understat.com/) 35 | 36 | Let's get you started with the package! 37 | 38 | # Getting Started With ggshakeR 39 | 40 | First things first, install R and RStudio. R is the actual language while RStudio is the IDE that will help you work with the R programming language in a nice way. 41 | 42 | Here are links to download both: 43 | 44 | * [R](https://www.r-project.org/) 45 | * [RStudio](https://www.rstudio.com/products/rstudio/download/) 46 | 47 | OK, now how do you get ggshakeR into RStudio? 48 | 49 | First install the devtools package: 50 | 51 | ```{r} 52 | install.packages("devtools") 53 | ``` 54 | 55 | After that, write the actual code to install ggshakeR: 56 | 57 | ```{r} 58 | devtools::install_github("abhiamishra/ggshakeR") 59 | ``` 60 | 61 | There were many __BIG__ changes made in __version 0.2.0__. Do note that the changes are permanent and will be the standard going forward. So please take the time to read the [Guide to Version 0.2.0 Vignette]() and the package documentation carefully so that you can transition over to the new argument syntax and make use of the new functionality that version `0.2.0` (and beyond!) will provide. If you want to go back to version `0.1.2` after you've already installed `0.2.0` (because you're not yet ready to edit your scripts to the new syntax or whatever), you can __re-install__ version `0.1.2` by running this: 62 | 63 | ```{r} 64 | devtools::install_github("abhiamishra/ggshakeR@0.1.2") 65 | ``` 66 | 67 | Now the package is installed, but it's not in your session just yet! When a package is in a session means you can actually use the package: 68 | 69 | ```{r} 70 | library(ggshakeR) 71 | ``` 72 | 73 | And that's it you're done! 74 | 75 | We hope you have fun! 76 | 77 | ## Helpful Links 78 | Here are some helpful links to get you started with the package: 79 | 80 | * [Guide To Pitch Plots](https://abhiamishra.github.io/ggshakeR/articles/Guide_to_Pitch_Plots.html) 81 | * [Guide To Expected Threat](https://abhiamishra.github.io/ggshakeR/articles/Guide_to_Exp_Threat.html) 82 | * [Guide To Pizza Plots](https://abhiamishra.github.io/ggshakeR/articles/Guide_to_PizzaPlots.html) 83 | * [Guide To Expected Possession Value](https://abhiamishra.github.io/ggshakeR/articles/Guide_to_EPV.html) 84 | * [Guide To Version 0.2.0](https://abhiamishra.github.io/ggshakeR/articles/Guide_to_Version_0-2-0.html) 85 | 86 | # Credit 87 | Thank you to StatsBomb and [Ewan Henderson](https://github.com/ewenme) for their respective packages. A big thank you to Ninad Barbadikar for creating the beautiful logo and helping with the package in general. Thank you [Jase Ziv](https://github.com/JaseZiv) for your help and your package [WorldFootballR](https://github.com/JaseZiv/worldfootballR) from which this took inspiration from. 88 | 89 | # Contact and Support 90 | Tell your friends about ggshakeR and shake theiR lives away! 91 | You can contact me at [email](abhiamishra0@gmail.com) or at my [Twitter](https://twitter.com/MishraAbhiA) 92 | 93 | Powered by Chatalytics:tm: 94 | 95 | Owner and Creator: [__Abhishek Amol Mishra__](https://github.com/abhiamishra) 96 | 97 | Authors: [__Harsh Krishna__](https://github.com/harshkrishna17) & [__Ryo Nakagawara__](https://github.com/Ryo-N7) 98 | 99 | # Licenses Used 100 | * [Torvaney](https://github.com/Torvaney/ggsoccer) and the ggsoccer package has been used in the package for creation of pitches. The use of this adheres to the [MIT License](https://github.com/Torvaney/ggsoccer/blob/master/LICENSE.md) provided. 101 | 102 | Licensing Copyright Statement: 103 | Copyright (c) 2019 Benjamin Torvaney 104 | 105 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 106 | 107 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 108 | 109 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 110 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # ggshakeR 5 | 6 | 7 | 8 | [![Lifecycle: 9 | experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://lifecycle.r-lib.org/articles/stages.html#experimental) 10 | [![License: 11 | MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 12 | [![check-standard](https://github.com/abhiamishra/ggshakeR/workflows/R-CMD-check/badge.svg)](https://github.com/abhiamishra/ggshakeR/actions) 13 | [![codecov](https://codecov.io/gh/abhiamishra/ggshakeR/branch/main/graph/badge.svg?token=GO5918U56P)](https://codecov.io/gh/abhiamishra/ggshakeR) 14 | 15 | 16 | Welcome to `ggshakeR`! 17 | 18 | This is R’s all-inclusive package for visualization and analysis of 19 | publicly available soccer data. 20 | 21 | This package aims to provide people interested in football analysis and 22 | visualization a platform to learn about it while also learning a new 23 | language at the same time. This package is designed to work with free 24 | soccer data and, for now, include: 25 | 26 | - [FBref](https://fbref.com/en/) 27 | - [StatsBomb Open Dataset](https://github.com/statsbomb/StatsBombR) 28 | - [Understat](https://understat.com/) 29 | 30 | Let’s get you started with the package! 31 | 32 | # Getting Started With ggshakeR 33 | 34 | First things first, install R and RStudio. R is the actual language 35 | while RStudio is the IDE that will help you work with the R programming 36 | language in a nice way. 37 | 38 | Here are links to download both: 39 | 40 | - [R](https://www.r-project.org/) 41 | - [RStudio](https://www.rstudio.com/products/rstudio/download/) 42 | 43 | OK, now how do you get ggshakeR into RStudio? 44 | 45 | First install the devtools package: 46 | 47 | ``` r 48 | install.packages("devtools") 49 | ``` 50 | 51 | After that, write the actual code to install ggshakeR: 52 | 53 | ``` r 54 | devtools::install_github("abhiamishra/ggshakeR") 55 | ``` 56 | 57 | There were many **BIG** changes made in **version 0.2.0**. Do note that 58 | the changes are permanent and will be the standard going forward. So 59 | please take the time to read the [Guide to Version 0.2.0 Vignette]() and 60 | the package documentation carefully so that you can transition over to 61 | the new argument syntax and make use of the new functionality that 62 | version `0.2.0` (and beyond!) will provide. If you want to go back to 63 | version `0.1.2` after you’ve already installed `0.2.0` (because you’re 64 | not yet ready to edit your scripts to the new syntax or whatever), you 65 | can **re-install** version `0.1.2` by running this: 66 | 67 | ``` r 68 | devtools::install_github("abhiamishra/ggshakeR@0.1.2") 69 | ``` 70 | 71 | Now the package is installed, but it’s not in your session just yet! 72 | When a package is in a session means you can actually use the package: 73 | 74 | ``` r 75 | library(ggshakeR) 76 | ``` 77 | 78 | And that’s it you’re done! 79 | 80 | We hope you have fun! 81 | 82 | ## Helpful Links 83 | 84 | Here are some helpful links to get you started with the package: 85 | 86 | - [Guide To Pitch 87 | Plots](https://abhiamishra.github.io/ggshakeR/articles/Guide_to_Pitch_Plots.html) 88 | - [Guide To Expected 89 | Threat](https://abhiamishra.github.io/ggshakeR/articles/Guide_to_Exp_Threat.html) 90 | - [Guide To Pizza 91 | Plots](https://abhiamishra.github.io/ggshakeR/articles/Guide_to_PizzaPlots.html) 92 | - [Guide To Expected Possession 93 | Value](https://abhiamishra.github.io/ggshakeR/articles/Guide_to_EPV.html) 94 | - [Guide To Version 95 | 0.2.0](https://abhiamishra.github.io/ggshakeR/articles/Guide_to_Version_0-2-0.html) 96 | 97 | # Credit 98 | 99 | Thank you to StatsBomb and [Ewan Henderson](https://github.com/ewenme) 100 | for their respective packages. A big thank you to Ninad Barbadikar for 101 | creating the beautiful logo and helping with the package in general. 102 | Thank you [Jase Ziv](https://github.com/JaseZiv) for your help and your 103 | package [WorldFootballR](https://github.com/JaseZiv/worldfootballR) from 104 | which this took inspiration from. 105 | 106 | # Contact and Support 107 | 108 | Tell your friends about ggshakeR and shake theiR lives away! You can 109 | contact me at [email](abhiamishra0@gmail.com) or at my 110 | [Twitter](https://twitter.com/MishraAbhiA) 111 | 112 | Powered by Chatalytics:tm: 113 | 114 | Owner and Creator: [**Abhishek Amol 115 | Mishra**](https://github.com/abhiamishra) 116 | 117 | Authors: [**Harsh Krishna**](https://github.com/harshkrishna17) & [**Ryo 118 | Nakagawara**](https://github.com/Ryo-N7) 119 | 120 | # Licenses Used 121 | 122 | - [Torvaney](https://github.com/Torvaney/ggsoccer) and the ggsoccer 123 | package has been used in the package for creation of pitches. The 124 | use of this adheres to the [MIT 125 | License](https://github.com/Torvaney/ggsoccer/blob/master/LICENSE.md) 126 | provided. 127 | 128 | Licensing Copyright Statement: Copyright (c) 2019 Benjamin Torvaney 129 | 130 | Permission is hereby granted, free of charge, to any person obtaining a 131 | copy of this software and associated documentation files (the 132 | “Software”), to deal in the Software without restriction, including 133 | without limitation the rights to use, copy, modify, merge, publish, 134 | distribute, sublicense, and/or sell copies of the Software, and to 135 | permit persons to whom the Software is furnished to do so, subject to 136 | the following conditions: 137 | 138 | The above copyright notice and this permission notice shall be included 139 | in all copies or substantial portions of the Software. 140 | 141 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS 142 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 143 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 144 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 145 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 146 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 147 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 148 | -------------------------------------------------------------------------------- /_pkgdown.yml: -------------------------------------------------------------------------------- 1 | url: https://abhiamishra.github.io/ggshakeR 2 | 3 | template: 4 | bootstrap: 5 5 | bslib: 6 | bg: "#0d1117" 7 | fg: "#FFFFFF" 8 | primary: "#FFFFFF" 9 | text-muted: "#FFFFFF" 10 | text-secondary-color: "#44A2A7" 11 | link-hover-color: "#44A2A7" 12 | navbar-bg: "#78d4d8" 13 | headings-color: "#FFFFFF" 14 | 15 | navbar: 16 | title: ggshakeR 17 | type: inverse 18 | left: 19 | - text: Home 20 | href: index.html 21 | - text: Reference 22 | href: reference/index.html 23 | - text: Guides 24 | menu: 25 | - text: Guide to EPV 26 | href: articles/Guide_to_EPV.html 27 | - text: Guide to Expected Threat 28 | href: articles/Guide_to_Exp_Threat.html 29 | - text: Guide to Pitch Plots 30 | href: articles/Guide_to_Pitch_Plots.html 31 | - text: Guide to Pizza Plots 32 | href: articles/Guide_to_PizzaPlots.html 33 | - text: Guide to Version 0.2.0 34 | href: articles/Guide_to_Version_0-2-0.html 35 | - text: News 36 | href: news/index.html 37 | - text: Library 38 | href: https://ggshaker.github.io/ 39 | 40 | reference: 41 | - title: Pitch Plots 42 | desc: Plots on a football/soccer pitch! 43 | contents: 44 | - plot_convexhull 45 | - plot_heatmap 46 | - plot_pass 47 | - plot_passflow 48 | - plot_passnet 49 | - plot_shot 50 | - plot_sonar 51 | - plot_voronoi 52 | - title: Analysis Plots 53 | desc: Into the Scattergram! 54 | contents: 55 | - plot_pizza 56 | - plot_scatter 57 | - plot_timeline 58 | - plot_trendline 59 | - title: Expected Threat & Possession Value (xT & EPV) 60 | desc: Functions for xT and EPV analysis 61 | contents: 62 | - calculate_epv 63 | - calculate_threat 64 | - title: Data Frames 65 | desc: Data frames housed within the package 66 | contents: 67 | - EPVGrid 68 | - xTGrid 69 | - SampleEventData 70 | - SampleSBData 71 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | 3 | coverage: 4 | status: 5 | project: 6 | default: 7 | target: auto 8 | threshold: 1% 9 | informational: true 10 | patch: 11 | default: 12 | target: auto 13 | threshold: 1% 14 | informational: true 15 | -------------------------------------------------------------------------------- /data-raw/DATASET.R: -------------------------------------------------------------------------------- 1 | ## code to prepare `DATASET` dataset goes here 2 | xTGrid <- read.csv("./inst/extdata/xT.csv") 3 | usethis::use_data(xTGrid, overwrite = TRUE, compress = "xz", header = FALSE) 4 | 5 | EPVGrid <- read.csv("./inst/extdata/EPV.csv", header = FALSE) 6 | usethis::use_data(EPVGrid, overwrite = TRUE, compress = "xz") 7 | 8 | SampleEventData <- read.csv("./inst/extdata/SampleEventData.csv") 9 | usethis::use_data(SampleEventData, overwrite = TRUE, compress = "xz") 10 | 11 | SampleSBData <- base::readRDS("./inst/testdata/sbevents.RDS") 12 | usethis::use_data(SampleSBData, overwrite = TRUE, compress = "xz") 13 | -------------------------------------------------------------------------------- /data/EPVGrid.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/data/EPVGrid.rda -------------------------------------------------------------------------------- /data/SampleEventData.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/data/SampleEventData.rda -------------------------------------------------------------------------------- /data/SampleSBData.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/data/SampleSBData.rda -------------------------------------------------------------------------------- /data/xTGrid.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/data/xTGrid.rda -------------------------------------------------------------------------------- /docs/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/docs/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /docs/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/docs/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /docs/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/docs/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /docs/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/docs/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /docs/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/docs/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /docs/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/docs/apple-touch-icon.png -------------------------------------------------------------------------------- /docs/articles/index.html: -------------------------------------------------------------------------------- 1 | 2 | Articles • ggshakeR 6 | Skip to contents 7 | 8 | 9 |
57 |
58 |
61 | 62 | 77 |
78 | 79 | 80 |
83 | 84 | 87 | 88 |
89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /docs/deps/data-deps.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/extra.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Titillium+Web&display=swap'); 2 | @import url("https://cdn.rawgit.com/tonsky/FiraCode/1.205/distr/fira_code.css"); 3 | 4 | body, .navbar-inverse { 5 | font-family: 'Titillium Web', sans-serif; 6 | font-weight: 400; 7 | } 8 | 9 | h1, h2, h3, h4, .h1, .h2, .h3, .h4 { 10 | font-family: 'Titillium Web Bold', sans-serif; 11 | font-weight: 700; 12 | } 13 | 14 | pre, code { 15 | font-family: "Fira Code", Consolas, Inconsolata, monospace; 16 | } 17 | 18 | .label-default { 19 | background-color: transparent; 20 | } 21 | 22 | .navbar-inverse { 23 | border-color: transparent; 24 | min-height: 50px; 25 | } 26 | 27 | .navbar-brand { 28 | background-image: url(logo.png); 29 | background-size: 32px auto; 30 | background-repeat: no-repeat; 31 | background-position: 15px center; 32 | padding: 0 0 0 54px; 33 | height: 50px; 34 | line-height: 50px; 35 | } 36 | 37 | .template-home img.logo, img.logo { 38 | height: 155px; 39 | width: 140px; 40 | } 41 | -------------------------------------------------------------------------------- /docs/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/docs/favicon-16x16.png -------------------------------------------------------------------------------- /docs/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/docs/favicon-32x32.png -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/docs/favicon.ico -------------------------------------------------------------------------------- /docs/link.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/docs/logo.png -------------------------------------------------------------------------------- /docs/pkgdown.js: -------------------------------------------------------------------------------- 1 | /* http://gregfranko.com/blog/jquery-best-practices/ */ 2 | (function($) { 3 | $(function() { 4 | 5 | $('nav.navbar').headroom(); 6 | 7 | Toc.init({ 8 | $nav: $("#toc"), 9 | $scope: $("main h2, main h3, main h4, main h5, main h6") 10 | }); 11 | 12 | if ($('#toc').length) { 13 | $('body').scrollspy({ 14 | target: '#toc', 15 | offset: $("nav.navbar").outerHeight() + 1 16 | }); 17 | } 18 | 19 | // Activate popovers 20 | $('[data-bs-toggle="popover"]').popover({ 21 | container: 'body', 22 | html: true, 23 | trigger: 'focus', 24 | placement: "top", 25 | sanitize: false, 26 | }); 27 | 28 | $('[data-bs-toggle="tooltip"]').tooltip(); 29 | 30 | /* Clipboard --------------------------*/ 31 | 32 | function changeTooltipMessage(element, msg) { 33 | var tooltipOriginalTitle=element.getAttribute('data-original-title'); 34 | element.setAttribute('data-original-title', msg); 35 | $(element).tooltip('show'); 36 | element.setAttribute('data-original-title', tooltipOriginalTitle); 37 | } 38 | 39 | if(ClipboardJS.isSupported()) { 40 | $(document).ready(function() { 41 | var copyButton = ""; 42 | 43 | $("div.sourceCode").addClass("hasCopyButton"); 44 | 45 | // Insert copy buttons: 46 | $(copyButton).prependTo(".hasCopyButton"); 47 | 48 | // Initialize tooltips: 49 | $('.btn-copy-ex').tooltip({container: 'body'}); 50 | 51 | // Initialize clipboard: 52 | var clipboard = new ClipboardJS('[data-clipboard-copy]', { 53 | text: function(trigger) { 54 | return trigger.parentNode.textContent.replace(/\n#>[^\n]*/g, ""); 55 | } 56 | }); 57 | 58 | clipboard.on('success', function(e) { 59 | changeTooltipMessage(e.trigger, 'Copied!'); 60 | e.clearSelection(); 61 | }); 62 | 63 | clipboard.on('error', function() { 64 | changeTooltipMessage(e.trigger,'Press Ctrl+C or Command+C to copy'); 65 | }); 66 | 67 | }); 68 | } 69 | 70 | /* Search marking --------------------------*/ 71 | var url = new URL(window.location.href); 72 | var toMark = url.searchParams.get("q"); 73 | var mark = new Mark("main#main"); 74 | if (toMark) { 75 | mark.mark(toMark, { 76 | accuracy: { 77 | value: "complementary", 78 | limiters: [",", ".", ":", "/"], 79 | } 80 | }); 81 | } 82 | 83 | /* Search --------------------------*/ 84 | /* Adapted from https://github.com/rstudio/bookdown/blob/2d692ba4b61f1e466c92e78fd712b0ab08c11d31/inst/resources/bs4_book/bs4_book.js#L25 */ 85 | // Initialise search index on focus 86 | var fuse; 87 | $("#search-input").focus(async function(e) { 88 | if (fuse) { 89 | return; 90 | } 91 | 92 | $(e.target).addClass("loading"); 93 | var response = await fetch($("#search-input").data("search-index")); 94 | var data = await response.json(); 95 | 96 | var options = { 97 | keys: ["what", "text", "code"], 98 | ignoreLocation: true, 99 | threshold: 0.1, 100 | includeMatches: true, 101 | includeScore: true, 102 | }; 103 | fuse = new Fuse(data, options); 104 | 105 | $(e.target).removeClass("loading"); 106 | }); 107 | 108 | // Use algolia autocomplete 109 | var options = { 110 | autoselect: true, 111 | debug: true, 112 | hint: false, 113 | minLength: 2, 114 | }; 115 | var q; 116 | async function searchFuse(query, callback) { 117 | await fuse; 118 | 119 | var items; 120 | if (!fuse) { 121 | items = []; 122 | } else { 123 | q = query; 124 | var results = fuse.search(query, { limit: 20 }); 125 | items = results 126 | .filter((x) => x.score <= 0.75) 127 | .map((x) => x.item); 128 | if (items.length === 0) { 129 | items = [{dir:"Sorry 😿",previous_headings:"",title:"No results found.",what:"No results found.",path:window.location.href}]; 130 | } 131 | } 132 | callback(items); 133 | } 134 | $("#search-input").autocomplete(options, [ 135 | { 136 | name: "content", 137 | source: searchFuse, 138 | templates: { 139 | suggestion: (s) => { 140 | if (s.title == s.what) { 141 | return `${s.dir} >
${s.title}
`; 142 | } else if (s.previous_headings == "") { 143 | return `${s.dir} >
${s.title}
> ${s.what}`; 144 | } else { 145 | return `${s.dir} >
${s.title}
> ${s.previous_headings} > ${s.what}`; 146 | } 147 | }, 148 | }, 149 | }, 150 | ]).on('autocomplete:selected', function(event, s) { 151 | window.location.href = s.path + "?q=" + q + "#" + s.id; 152 | }); 153 | }); 154 | })(window.jQuery || window.$) 155 | 156 | 157 | -------------------------------------------------------------------------------- /docs/pkgdown.yml: -------------------------------------------------------------------------------- 1 | pandoc: 2.17.1.1 2 | pkgdown: 2.0.4 3 | pkgdown_sha: ~ 4 | articles: 5 | Guide_to_EPV: Guide_to_EPV.html 6 | Guide_to_Exp_Threat: Guide_to_Exp_Threat.html 7 | Guide_to_Pitch_Plots: Guide_to_Pitch_Plots.html 8 | Guide_to_PizzaPlots: Guide_to_PizzaPlots.html 9 | Guide_to_Version_0-2-0: Guide_to_Version_0-2-0.html 10 | last_built: 2023-08-18T13:53Z 11 | urls: 12 | reference: https://abhiamishra.github.io/ggshakeR/reference 13 | article: https://abhiamishra.github.io/ggshakeR/articles 14 | 15 | -------------------------------------------------------------------------------- /docs/reference/Rplot001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/docs/reference/Rplot001.png -------------------------------------------------------------------------------- /docs/reference/TestEventData.html: -------------------------------------------------------------------------------- 1 | 2 | Sample event data frame — TestEventData • ggshakeR 6 | Skip to contents 7 | 8 | 9 |
47 |
48 |
53 | 54 |
55 |

Event dataframe to play with and understand usage of functions

56 |
57 | 58 |
59 |

Usage

60 |
TestEventData
61 |
62 | 63 |
64 |

Format

65 |

A dataframe with the columns x, y, finalX, finalY, minute, 66 | playerId, teamId, outcome and type.

67 |
68 | 69 |
71 | 72 | 73 |
76 | 77 | 80 | 81 |
82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /docs/reference/figures/Convexhull.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/docs/reference/figures/Convexhull.png -------------------------------------------------------------------------------- /docs/reference/figures/Convexhull_JordiAlba.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/docs/reference/figures/Convexhull_JordiAlba.png -------------------------------------------------------------------------------- /docs/reference/figures/Opta_Passnet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/docs/reference/figures/Opta_Passnet.png -------------------------------------------------------------------------------- /docs/reference/figures/Passnet_Opta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/docs/reference/figures/Passnet_Opta.png -------------------------------------------------------------------------------- /docs/reference/figures/Passnet_SB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/docs/reference/figures/Passnet_SB.png -------------------------------------------------------------------------------- /docs/reference/figures/SB_Passnet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/docs/reference/figures/SB_Passnet.png -------------------------------------------------------------------------------- /docs/reference/figures/addimage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/docs/reference/figures/addimage.png -------------------------------------------------------------------------------- /docs/reference/figures/calculate_epv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/docs/reference/figures/calculate_epv.png -------------------------------------------------------------------------------- /docs/reference/figures/calculate_threat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/docs/reference/figures/calculate_threat.png -------------------------------------------------------------------------------- /docs/reference/figures/comp_pizza_cus1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/docs/reference/figures/comp_pizza_cus1.png -------------------------------------------------------------------------------- /docs/reference/figures/comp_pizza_cus2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/docs/reference/figures/comp_pizza_cus2.png -------------------------------------------------------------------------------- /docs/reference/figures/comp_pizza_plot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/docs/reference/figures/comp_pizza_plot.png -------------------------------------------------------------------------------- /docs/reference/figures/comp_pizza_plot_cus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/docs/reference/figures/comp_pizza_plot_cus.png -------------------------------------------------------------------------------- /docs/reference/figures/ggshakeRhex-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/docs/reference/figures/ggshakeRhex-small.png -------------------------------------------------------------------------------- /docs/reference/figures/ggshakeRhex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/docs/reference/figures/ggshakeRhex.png -------------------------------------------------------------------------------- /docs/reference/figures/ggshakeRlogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/docs/reference/figures/ggshakeRlogo.png -------------------------------------------------------------------------------- /docs/reference/figures/levelone_predeploy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/docs/reference/figures/levelone_predeploy.png -------------------------------------------------------------------------------- /docs/reference/figures/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/docs/reference/figures/logo.png -------------------------------------------------------------------------------- /docs/reference/figures/pizza_plot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/docs/reference/figures/pizza_plot.png -------------------------------------------------------------------------------- /docs/reference/figures/pizza_plot_comparison.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/docs/reference/figures/pizza_plot_comparison.png -------------------------------------------------------------------------------- /docs/reference/figures/pizza_plot_cus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/docs/reference/figures/pizza_plot_cus.png -------------------------------------------------------------------------------- /docs/reference/figures/plot_heatmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/docs/reference/figures/plot_heatmap.png -------------------------------------------------------------------------------- /docs/reference/figures/plot_pass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/docs/reference/figures/plot_pass.png -------------------------------------------------------------------------------- /docs/reference/figures/plot_passflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/docs/reference/figures/plot_passflow.png -------------------------------------------------------------------------------- /docs/reference/figures/plot_shot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/docs/reference/figures/plot_shot.png -------------------------------------------------------------------------------- /docs/reference/figures/plot_sonar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/docs/reference/figures/plot_sonar.png -------------------------------------------------------------------------------- /docs/reference/figures/plot_voronoi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/docs/reference/figures/plot_voronoi.png -------------------------------------------------------------------------------- /docs/reference/figures/plot_voronoi_fill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/docs/reference/figures/plot_voronoi_fill.png -------------------------------------------------------------------------------- /docs/reference/figures/shakeRlogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/docs/reference/figures/shakeRlogo.png -------------------------------------------------------------------------------- /docs/reference/figures/xTGrid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/docs/reference/figures/xTGrid.png -------------------------------------------------------------------------------- /docs/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | https://abhiamishra.github.io/ggshakeR/404.html 5 | 6 | 7 | https://abhiamishra.github.io/ggshakeR/articles/Guide_to_EPV.html 8 | 9 | 10 | https://abhiamishra.github.io/ggshakeR/articles/Guide_to_Exp_Threat.html 11 | 12 | 13 | https://abhiamishra.github.io/ggshakeR/articles/Guide_to_Pitch_Plots.html 14 | 15 | 16 | https://abhiamishra.github.io/ggshakeR/articles/Guide_to_PizzaPlots.html 17 | 18 | 19 | https://abhiamishra.github.io/ggshakeR/articles/Guide_to_Version_0-2-0.html 20 | 21 | 22 | https://abhiamishra.github.io/ggshakeR/articles/index.html 23 | 24 | 25 | https://abhiamishra.github.io/ggshakeR/authors.html 26 | 27 | 28 | https://abhiamishra.github.io/ggshakeR/CODE_OF_CONDUCT.html 29 | 30 | 31 | https://abhiamishra.github.io/ggshakeR/index.html 32 | 33 | 34 | https://abhiamishra.github.io/ggshakeR/LICENSE-text.html 35 | 36 | 37 | https://abhiamishra.github.io/ggshakeR/LICENSE.html 38 | 39 | 40 | https://abhiamishra.github.io/ggshakeR/news/index.html 41 | 42 | 43 | https://abhiamishra.github.io/ggshakeR/pull_request_template.html 44 | 45 | 46 | https://abhiamishra.github.io/ggshakeR/reference/calculate_epv.html 47 | 48 | 49 | https://abhiamishra.github.io/ggshakeR/reference/calculate_threat.html 50 | 51 | 52 | https://abhiamishra.github.io/ggshakeR/reference/EPVGrid.html 53 | 54 | 55 | https://abhiamishra.github.io/ggshakeR/reference/hull_fun.html 56 | 57 | 58 | https://abhiamishra.github.io/ggshakeR/reference/index.html 59 | 60 | 61 | https://abhiamishra.github.io/ggshakeR/reference/plot_convexhull.html 62 | 63 | 64 | https://abhiamishra.github.io/ggshakeR/reference/plot_heatmap.html 65 | 66 | 67 | https://abhiamishra.github.io/ggshakeR/reference/plot_pass.html 68 | 69 | 70 | https://abhiamishra.github.io/ggshakeR/reference/plot_passflow.html 71 | 72 | 73 | https://abhiamishra.github.io/ggshakeR/reference/plot_passnet.html 74 | 75 | 76 | https://abhiamishra.github.io/ggshakeR/reference/plot_pizza.html 77 | 78 | 79 | https://abhiamishra.github.io/ggshakeR/reference/plot_scatter.html 80 | 81 | 82 | https://abhiamishra.github.io/ggshakeR/reference/plot_shot.html 83 | 84 | 85 | https://abhiamishra.github.io/ggshakeR/reference/plot_sonar.html 86 | 87 | 88 | https://abhiamishra.github.io/ggshakeR/reference/plot_timeline.html 89 | 90 | 91 | https://abhiamishra.github.io/ggshakeR/reference/plot_trendline.html 92 | 93 | 94 | https://abhiamishra.github.io/ggshakeR/reference/plot_voronoi.html 95 | 96 | 97 | https://abhiamishra.github.io/ggshakeR/reference/SampleEventData.html 98 | 99 | 100 | https://abhiamishra.github.io/ggshakeR/reference/SampleSBData.html 101 | 102 | 103 | https://abhiamishra.github.io/ggshakeR/reference/shift_column.html 104 | 105 | 106 | https://abhiamishra.github.io/ggshakeR/reference/TestEventData.html 107 | 108 | 109 | https://abhiamishra.github.io/ggshakeR/reference/text_wrap.html 110 | 111 | 112 | https://abhiamishra.github.io/ggshakeR/reference/viridis_d_pal.html 113 | 114 | 115 | https://abhiamishra.github.io/ggshakeR/reference/xTGrid.html 116 | 117 | 118 | https://abhiamishra.github.io/ggshakeR/reference/zissou_pal.html 119 | 120 | 121 | -------------------------------------------------------------------------------- /ggshakeR.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | BuildType: Package 16 | PackageUseDevtools: Yes 17 | PackageInstallArgs: --no-multiarch --with-keep.source 18 | PackageRoxygenize: rd,collate,namespace 19 | -------------------------------------------------------------------------------- /inst/extdata/xT.csv: -------------------------------------------------------------------------------- 1 | 0,1,2,3,4,5,6,7,8,9,10,11 2 | 0.00638303,0.00779616,0.00844854,0.00977659,0.01126267,0.01248344,0.01473596,0.0174506,0.02122129,0.02756312,0.03485072,0.0379259 3 | 0.00750072,0.00878589,0.00942382,0.0105949,0.01214719,0.0138454,0.01611813,0.01870347,0.02401521,0.02953272,0.04066992,0.04647721 4 | 0.0088799,0.00977745,0.01001304,0.01110462,0.01269174,0.01429128,0.01685596,0.01935132,0.0241224,0.02855202,0.05491138,0.06442595 5 | 0.00941056,0.01082722,0.01016549,0.01132376,0.01262646,0.01484598,0.01689528,0.0199707,0.02385149,0.03511326,0.10805102,0.25745362 6 | 0.00941056,0.01082722,0.01016549,0.01132376,0.01262646,0.01484598,0.01689528,0.0199707,0.02385149,0.03511326,0.10805102,0.25745362 7 | 0.0088799,0.00977745,0.01001304,0.01110462,0.01269174,0.01429128,0.01685596,0.01935132,0.0241224,0.02855202,0.05491138,0.06442595 8 | 0.00750072,0.00878589,0.00942382,0.0105949,0.01214719,0.0138454,0.01611813,0.01870347,0.02401521,0.02953272,0.04066992,0.04647721 9 | 0.00638303,0.00779616,0.00844854,0.00977659,0.01126267,0.01248344,0.01473596,0.0174506,0.02122129,0.02756312,0.03485072,0.0379259 10 | -------------------------------------------------------------------------------- /inst/planning_guide.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/inst/planning_guide.xlsx -------------------------------------------------------------------------------- /inst/testdata/ilkay.RDS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/inst/testdata/ilkay.RDS -------------------------------------------------------------------------------- /inst/testdata/laliga2022.RDS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/inst/testdata/laliga2022.RDS -------------------------------------------------------------------------------- /inst/testdata/nico.RDS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/inst/testdata/nico.RDS -------------------------------------------------------------------------------- /inst/testdata/sbevents.RDS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/inst/testdata/sbevents.RDS -------------------------------------------------------------------------------- /inst/testdata/shot_data.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/inst/testdata/shot_data.rds -------------------------------------------------------------------------------- /man/EPVGrid.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/epv_guide.R 3 | \docType{data} 4 | \name{EPVGrid} 5 | \alias{EPVGrid} 6 | \title{EPV calculation data for analysis} 7 | \format{ 8 | A \code{dataframe} with 50 columns and 31 rows spanning 9 | a football pitch. 10 | } 11 | \usage{ 12 | EPVGrid 13 | } 14 | \description{ 15 | a simple \code{dataframe} that is used for 16 | calculating expected possession values 17 | } 18 | \keyword{datasets} 19 | -------------------------------------------------------------------------------- /man/SampleEventData.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/sample_event_data_guide.R 3 | \docType{data} 4 | \name{SampleEventData} 5 | \alias{SampleEventData} 6 | \title{Sample event data frame} 7 | \format{ 8 | A \code{dataframe} with the columns \code{x}, \code{y}, \code{finalX}, \code{finalY}, \code{minute}, 9 | \code{playerId}, \code{teamId}, \code{outcome} and \code{type}. 10 | } 11 | \usage{ 12 | SampleEventData 13 | } 14 | \description{ 15 | Event \code{dataframe} to play with and understand usage of functions 16 | } 17 | \keyword{datasets} 18 | -------------------------------------------------------------------------------- /man/SampleSBData.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/sample_sbevent_data_guide.R 3 | \docType{data} 4 | \name{SampleSBData} 5 | \alias{SampleSBData} 6 | \title{Sample StatsBomb event data frame} 7 | \format{ 8 | A \code{dataframe} with the columns \code{x}, \code{y}, \code{finalX}, \code{finalY}, and the rest of StatsBomb columns 9 | } 10 | \usage{ 11 | SampleSBData 12 | } 13 | \description{ 14 | StatsBomb event \code{dataframe} to play with and understand usage of functions. 15 | The following code was used with the use of {StatsBombR}: 16 | 17 | \code{ Comp <- FreeCompetitions() \%>\% } 18 | \code{ filter(competition_id == 11 & season_name == "2014/2015") } 19 | \code{ Matches <- FreeMatches(Comp) } 20 | \code{ StatsBombData <- StatsBombFreeEvents(MatchesDF = Matches, Parallel = TRUE) } 21 | \code{ plotting_data <- allclean(StatsBombData) } 22 | \code{ plotting_data <- plotting_data \%>\% } 23 | \code{ rename("x" = "location.x", } 24 | \code{ "y" = "location.y", } 25 | \code{ "finalX" = "pass.end_location.x", } 26 | \code{ "finalY" = "pass.end_location.y") } 27 | } 28 | \keyword{datasets} 29 | -------------------------------------------------------------------------------- /man/calculate_epv.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/calculate_epv.R 3 | \name{calculate_epv} 4 | \alias{calculate_epv} 5 | \title{Calculating EPV for passes, carries, etc} 6 | \usage{ 7 | calculate_epv(data, type = "opta") 8 | } 9 | \arguments{ 10 | \item{data}{The dataframe that stores your data. Must contain starting x,y locations and ending x,y locations: \code{x}, \code{y}, \code{finalX}, \code{finalY}} 11 | 12 | \item{type}{indicator for what type of data the eventData. Currently, options include "opta" (default) and "statsbomb"} 13 | } 14 | \value{ 15 | returns a dataframe object 16 | } 17 | \description{ 18 | Calculating EPV for passes, carries, etc 19 | } 20 | \examples{ 21 | \dontrun{ 22 | endResult <- calculate_epv(test, type = "statsbomb") 23 | endResult 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /man/calculate_threat.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/calculate_threat.R 3 | \name{calculate_threat} 4 | \alias{calculate_threat} 5 | \title{Calculating xT for passes, carries, etc} 6 | \usage{ 7 | calculate_threat(data, type = "opta") 8 | } 9 | \arguments{ 10 | \item{data}{The dataframe that stores your data. Must contain starting x,y locations and ending x,y locations: \code{x}, \code{y}, \code{finalX}, \code{finalY}} 11 | 12 | \item{type}{indicator for what type of data the data. Currently, options include "opta" (default) and "statsbomb"} 13 | } 14 | \value{ 15 | returns a dataframe object 16 | } 17 | \description{ 18 | Calculating xT for passes, carries, etc 19 | } 20 | \examples{ 21 | \dontrun{ 22 | endResult <- calculate_threat(test, type = "statsbomb") 23 | endResult 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /man/figures/Convexhull.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/man/figures/Convexhull.png -------------------------------------------------------------------------------- /man/figures/Convexhull_JordiAlba.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/man/figures/Convexhull_JordiAlba.png -------------------------------------------------------------------------------- /man/figures/Opta_Passnet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/man/figures/Opta_Passnet.png -------------------------------------------------------------------------------- /man/figures/SB_Passnet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/man/figures/SB_Passnet.png -------------------------------------------------------------------------------- /man/figures/addimage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/man/figures/addimage.png -------------------------------------------------------------------------------- /man/figures/calculate_epv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/man/figures/calculate_epv.png -------------------------------------------------------------------------------- /man/figures/calculate_threat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/man/figures/calculate_threat.png -------------------------------------------------------------------------------- /man/figures/comp_pizza_plot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/man/figures/comp_pizza_plot.png -------------------------------------------------------------------------------- /man/figures/comp_pizza_plot_cus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/man/figures/comp_pizza_plot_cus.png -------------------------------------------------------------------------------- /man/figures/ggshakeRhex-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/man/figures/ggshakeRhex-small.png -------------------------------------------------------------------------------- /man/figures/ggshakeRhex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/man/figures/ggshakeRhex.png -------------------------------------------------------------------------------- /man/figures/ggshakeRlogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/man/figures/ggshakeRlogo.png -------------------------------------------------------------------------------- /man/figures/levelone_predeploy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/man/figures/levelone_predeploy.png -------------------------------------------------------------------------------- /man/figures/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/man/figures/logo.png -------------------------------------------------------------------------------- /man/figures/pizza_plot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/man/figures/pizza_plot.png -------------------------------------------------------------------------------- /man/figures/pizza_plot_cus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/man/figures/pizza_plot_cus.png -------------------------------------------------------------------------------- /man/figures/plot_heatmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/man/figures/plot_heatmap.png -------------------------------------------------------------------------------- /man/figures/plot_pass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/man/figures/plot_pass.png -------------------------------------------------------------------------------- /man/figures/plot_passflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/man/figures/plot_passflow.png -------------------------------------------------------------------------------- /man/figures/plot_shot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/man/figures/plot_shot.png -------------------------------------------------------------------------------- /man/figures/plot_sonar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/man/figures/plot_sonar.png -------------------------------------------------------------------------------- /man/figures/plot_voronoi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/man/figures/plot_voronoi.png -------------------------------------------------------------------------------- /man/figures/plot_voronoi_fill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/man/figures/plot_voronoi_fill.png -------------------------------------------------------------------------------- /man/figures/shakeRlogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/man/figures/shakeRlogo.png -------------------------------------------------------------------------------- /man/figures/xTGrid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/man/figures/xTGrid.png -------------------------------------------------------------------------------- /man/hull_fun.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{hull_fun} 4 | \alias{hull_fun} 5 | \title{hull_fun} 6 | \usage{ 7 | hull_fun(data) 8 | } 9 | \arguments{ 10 | \item{x}{data frame} 11 | } 12 | \value{ 13 | data frame 14 | } 15 | \description{ 16 | Create boundaries for convex hull 17 | } 18 | \keyword{internal} 19 | -------------------------------------------------------------------------------- /man/plot_convexhull.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/plot_convexhull.R 3 | \name{plot_convexhull} 4 | \alias{plot_convexhull} 5 | \title{Function for plotting convex hulls} 6 | \usage{ 7 | plot_convexhull( 8 | data, 9 | data_type = "statsbomb", 10 | color = "#E74C3C", 11 | title = "", 12 | theme = "dark" 13 | ) 14 | } 15 | \arguments{ 16 | \item{data}{Data frame that houses pass data. Opta dataframe must contain atleast the following columns: \code{x}, \code{y}, \code{finalX}, \code{finalY}, \code{playerId}} 17 | 18 | \item{data_type}{Type of data that is being put in: opta or statsbomb. Default set to "statsbomb"} 19 | 20 | \item{color}{The color of the outline of the convex hull} 21 | 22 | \item{title}{Title of the plot} 23 | 24 | \item{theme}{Indicates what theme the map must be shown in: dark (default), white, rose, almond} 25 | } 26 | \value{ 27 | a ggplot2 object 28 | } 29 | \description{ 30 | This function allows for data, that can be from Opta or StatsBomb, to be used 31 | for plotting convex hulls on top of an outline of a football pitch. 32 | } 33 | \examples{ 34 | \dontrun{ 35 | plot <- plot_convexhull(data = data, data_type = "opta", color = "blue", title = "Team 1") 36 | plot 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /man/plot_heatmap.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/plot_heatmap.R 3 | \name{plot_heatmap} 4 | \alias{plot_heatmap} 5 | \title{Plotting heatmap} 6 | \usage{ 7 | plot_heatmap( 8 | data, 9 | type = "", 10 | data_type = "statsbomb", 11 | binwidth = 20, 12 | theme = "" 13 | ) 14 | } 15 | \arguments{ 16 | \item{data}{The dataframe that stores your data. Dataframe must contain atleast the following columns: \code{x}, \code{y}.} 17 | 18 | \item{type}{indicates the type of heatmap to plot. "hex" indicates hex bins, "density" indicates density (default), 19 | "binwidth" indicates binwidth heatmap pass, and "jdp" indicates a binned heatmap according jdp pitch markings.} 20 | 21 | \item{data_type}{Type of data that is being put in: opta or statsbomb. Default set to "statsbomb"} 22 | 23 | \item{binwidth}{indicates the size of the bin width to construct heatmap for type "binwidth". The same argument name as the underlying call to \code{geom_bin2d()}. Default set to 20.} 24 | 25 | \item{theme}{indicates what theme the map must be shown in: dark (default), white, rose, almond} 26 | } 27 | \value{ 28 | returns a ggplot2 object 29 | } 30 | \description{ 31 | This function allows you to plot various types of heatmaps of starting x and y coordinates: 32 | hex binwidth heatmap, 33 | density heatmap, and 34 | binwidth heatmap 35 | } 36 | \examples{ 37 | \dontrun{ 38 | plot <- plot_heatmap(data = touchData, type = "hex") 39 | plot 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /man/plot_pass.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/plot_pass.R 3 | \name{plot_pass} 4 | \alias{plot_pass} 5 | \title{Plotting passes} 6 | \usage{ 7 | plot_pass( 8 | data, 9 | data_type = "statsbomb", 10 | type = "sep", 11 | progressive_pass = FALSE, 12 | cross = FALSE, 13 | shot = FALSE, 14 | switch = FALSE, 15 | outcome = "all", 16 | theme = "dark" 17 | ) 18 | } 19 | \arguments{ 20 | \item{data}{The data frame that stores your passing data. Opta data frame must contain at least the following columns: \code{x}, \code{y}, \code{finalX}, \code{finalY}} 21 | 22 | \item{data_type}{Type of data that is being put in: opta or statsbomb. Default set to "statsbomb"} 23 | 24 | \item{type}{indicates the type of plot to pass. "sep" separates successful and unsuccessful passes. "all" plots all passes on one pitch. Default = "sep". Only available for StatsBomb data} 25 | 26 | \item{progressive_pass}{indicates whether to map out progressive passes. Only available for StatsBomb data} 27 | 28 | \item{cross}{indicates whether to map out crosses. Only available for StatsBomb data} 29 | 30 | \item{shot}{indicates whether to map out shot assists. Only available for StatsBomb data} 31 | 32 | \item{switch}{indicates whether to map out switches of play. Only available for StatsBomb data} 33 | 34 | \item{outcome}{indicates whether you want successful ("suc"), unsuccessful ("unsuc"), or all ("all"). Only available for StatsBomb data} 35 | 36 | \item{theme}{indicates what theme the map must be shown in: dark (default), white, rose, almond} 37 | } 38 | \value{ 39 | returns a ggplot2 object 40 | } 41 | \description{ 42 | This function allows you to plot various types of plots 43 | that have passes as some sort of input. Data entered must have columns for which you want to plot with. 44 | Compatible with StatsBomb and Opta data. 45 | } 46 | \examples{ 47 | \dontrun{ 48 | plot <- plot_pass(data, type = "all", progressive_pass = TRUE) 49 | plot 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /man/plot_passflow.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/plot_passflow.R 3 | \name{plot_passflow} 4 | \alias{plot_passflow} 5 | \title{Passflow plot function} 6 | \usage{ 7 | plot_passflow(data, data_type = "statsbomb", binwidth = 0) 8 | } 9 | \arguments{ 10 | \item{data}{Dataframe that must house pass data only and must contain atleast the following columns: \code{x}, \code{y}, \code{finalX}, \code{finalY}} 11 | 12 | \item{data_type}{Type of data that is being put in: opta or statsbomb. Default set to "statsbomb"} 13 | 14 | \item{binwidth}{Details the bin size the passflow needs to bin to. The same argument name as the underlying call to \code{geom_bin2d()}. Default is 20.} 15 | } 16 | \value{ 17 | returns a ggplot2 object 18 | } 19 | \description{ 20 | This function takes in a dataframe and binsizes 21 | to make a passflow map. Compatible, for right now, with StatsBomb data only. Returns a ggplot object 22 | } 23 | \examples{ 24 | \dontrun{ 25 | plot <- plot_passflow(data, binwidth = 30) 26 | plot 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /man/plot_passnet.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/plot_passnet.R 3 | \name{plot_passnet} 4 | \alias{plot_passnet} 5 | \title{Function for plotting pass networks} 6 | \usage{ 7 | plot_passnet( 8 | data, 9 | data_type = "statsbomb", 10 | team_name, 11 | scale_stat = "xT", 12 | scale_color = "#E74C3C", 13 | subtitle = "", 14 | theme = "dark" 15 | ) 16 | } 17 | \arguments{ 18 | \item{data}{Data frame that houses pass data} 19 | 20 | \item{data_type}{Type of data that is being put in: opta or statsbomb. Default set to "statsbomb"} 21 | 22 | \item{team_name}{The name of the team of which you want a pass network} 23 | 24 | \item{scale_stat}{Stat for the player node color scale. Choose between "xT" and "EPV"} 25 | 26 | \item{scale_color}{Color of higher end of xT/EPV color scale. Default set to "#E74C3C"} 27 | 28 | \item{subtitle}{Subtitle of the pass network plot} 29 | 30 | \item{theme}{The background theme -> "dark" or "light"} 31 | } 32 | \value{ 33 | a ggplot2 object 34 | } 35 | \description{ 36 | This function allows for data, that can be from Opta or Statsbomb, to be used 37 | for plotting pass networks. 38 | } 39 | \examples{ 40 | \dontrun{ 41 | plot <- plot_passnet(data, data_type = "opta", team_name = "Arsenal") 42 | plot 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /man/plot_pizza.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/plot_pizza.R 3 | \name{plot_pizza} 4 | \alias{plot_pizza} 5 | \title{Function for plotting percentile/pizza plots} 6 | \usage{ 7 | plot_pizza( 8 | data, 9 | type = "", 10 | template, 11 | color_possession = "#41ab5d", 12 | color_attack = "#2171b5", 13 | color_defense = "#fec44f", 14 | player_1, 15 | player_2, 16 | color_compare = "#41ab5d", 17 | season = "Last 365 Days Men's Big 5 Leagues, UCL, UEL", 18 | season_player_1 = "Last 365 Days Men's Big 5 Leagues, UCL, UEL", 19 | season_player_2 = "Last 365 Days Men's Big 5 Leagues, UCL, UEL", 20 | theme = "" 21 | ) 22 | } 23 | \arguments{ 24 | \item{data}{Data frame can contain either one player or two depending on the type of plot made} 25 | 26 | \item{type}{Type of plot -> single and comparison} 27 | 28 | \item{template}{Selecting a group of pre-selected metrics for each position by position namely: 29 | outfielder, goalkeeper and custom} 30 | 31 | \item{color_possession}{Selecting the color for possession group of stats. To be used only for single player plot} 32 | 33 | \item{color_attack}{Selecting the color for attacking group of stats. To be used only for single player plot} 34 | 35 | \item{color_defense}{Selecting the color for defense group of stats. To be used only for single player plot} 36 | 37 | \item{player_1}{Selecting the first player. To be used only for comparison plot} 38 | 39 | \item{player_2}{Selecting the second player. To be used only for comparison plot} 40 | 41 | \item{color_compare}{Selecting the color of comparison to be used only for comparison plot} 42 | 43 | \item{season}{Specify what season to pick for a single player pizza chart. Pick the scouting period from the scouting period column in the data} 44 | 45 | \item{season_player_1}{Specify what season to pick for the first player in a pizza chart} 46 | 47 | \item{season_player_2}{Specify what season to pick for the second player in a pizza chart} 48 | 49 | \item{theme}{Specify the theme of the pizza chart -> dark, black, and white. Default set to dark} 50 | } 51 | \value{ 52 | a ggplot2 object 53 | } 54 | \description{ 55 | This function allows for data, that has to be scraped from FBref, to be used 56 | for plotting single and comparison percentile plots. 57 | } 58 | \details{ 59 | For best image quality use: 60 | ggsave("image.png", width = 2900, height = 2800, units = "px") 61 | } 62 | \examples{ 63 | \dontrun{ 64 | plot1 <- plot_pizza(data = data, type = "comparison", template = "outfielder", 65 | player_1 = "Nicolo Barella", player_2 = "Ilkay Gundogan", 66 | season_player_1 = "Last 365 Days Men's Big 5 Leagues, UCL, UEL", 67 | season_player_2 = "Last 365 Days Men's Big 5 Leagues, UCL, UEL", 68 | color_compare = "lightgreen", theme = "black") 69 | plot1 70 | 71 | plot2 <- plot_pizza(data = data1, type = "single", template = "outfielder", 72 | color_possession = "green", color_attack = "lightblue", 73 | season = "Last 365 Days Men's Big 5 Leagues, UCL, UEL", 74 | color_defense = "red", theme = "dark") 75 | plot2 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /man/plot_scatter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/plot_scatter.R 3 | \name{plot_scatter} 4 | \alias{plot_scatter} 5 | \title{Plotting scatter plots} 6 | \usage{ 7 | plot_scatter( 8 | data, 9 | x = "", 10 | y = "", 11 | label = "", 12 | set_size_num = 5, 13 | set_size_var = "", 14 | set_color_num = "red", 15 | set_color_var = "", 16 | title = "", 17 | title_size = 25, 18 | subtitle = "", 19 | subtitle_size = 15, 20 | caption = "", 21 | caption_size = 10, 22 | theme = "classic" 23 | ) 24 | } 25 | \arguments{ 26 | \item{data}{the dataframe passed in for plotting.} 27 | 28 | \item{x}{name of column name in data to be used on x-axis} 29 | 30 | \item{y}{name of column name in data to be used on y-axis} 31 | 32 | \item{label}{the name of column name in data to label the scatter plot} 33 | 34 | \item{set_size_num}{sets the size of the points set as a constant. Default size = 5.} 35 | 36 | \item{set_size_var}{Enter name of column name in data to set size based on variable.} 37 | 38 | \item{set_color_num}{sets the color of the points set as a constant. Can enter hexcode or a valid ggplot2 color. Default = "red"} 39 | 40 | \item{set_color_var}{Enter name of column name in data to set color based on variable.} 41 | 42 | \item{title}{pick the title of the scatter plot} 43 | 44 | \item{title_size}{sets the size of the title of the scatter plot. Default size = 25.} 45 | 46 | \item{subtitle}{pick the subtitle of the scatter plot} 47 | 48 | \item{subtitle_size}{sets the size of the subtitle of the scatter plot Default size = 15.} 49 | 50 | \item{caption}{pick the caption of the scatter plot} 51 | 52 | \item{caption_size}{sets the size of the caption of the scatter plot. Default size = 10.} 53 | 54 | \item{theme}{decide the theme of the plot between four choices: classic, minimal, grey, bw. Default = "classic"} 55 | } 56 | \value{ 57 | returns a ggplot2 object 58 | } 59 | \description{ 60 | This function allows you to plot various types of plots 61 | that have passes as some sort of input. Compatible with any data frame of any data type. Returns a ggplot object. 62 | } 63 | \examples{ 64 | \dontrun{ 65 | plot <- plot_scatter(data, x = "player", y = "age", label = "team") 66 | plot 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /man/plot_shot.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/plot_shot.R 3 | \name{plot_shot} 4 | \alias{plot_shot} 5 | \title{Function for plotting shots} 6 | \usage{ 7 | plot_shot( 8 | data, 9 | type = "", 10 | bins = 30, 11 | highlight_goals = "", 12 | average_location = "", 13 | theme = "" 14 | ) 15 | } 16 | \arguments{ 17 | \item{data}{Dataframe that houses shot data. Dataframe must contain atleast the following columns: X,Y,xG,result,name} 18 | 19 | \item{type}{Type of showcasing the shotmap: hexbin, density, point (default)} 20 | 21 | \item{bins}{Bin size for creating bins. Use this when using hexbin shotmap. The same argument name as the underlying call to \code{geom_hex()}. Default = 30.} 22 | 23 | \item{highlight_goals}{to choose to display only the goals in a different color.} 24 | 25 | \item{average_location}{for removing lines denoting average location of shots if need be.} 26 | 27 | \item{theme}{Theme preferences for display: dark (default), white, rose, almond} 28 | } 29 | \value{ 30 | a ggplot2 object 31 | } 32 | \description{ 33 | This function allows for data, that has to be scraped from Understat, to be used 34 | for plotting shots. 35 | } 36 | \examples{ 37 | \dontrun{ 38 | plot <- plot_shot(data, type = "hexbin", bins = 20, 39 | average_location = TRUE, highlight_goals = FALSE) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /man/plot_sonar.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/plot_sonar.R 3 | \name{plot_sonar} 4 | \alias{plot_sonar} 5 | \title{Function for plotting pass sonars} 6 | \usage{ 7 | plot_sonar(data, data_type = "statsbomb", title = "") 8 | } 9 | \arguments{ 10 | \item{data}{Dataframe that houses pass data. Dataframe must contain atleast the following columns: \code{x}, \code{y}, \code{finalX}, \code{finalY}} 11 | 12 | \item{data_type}{Type of data that is being put in: opta or statsbomb. Default set to "statsbomb"} 13 | 14 | \item{title}{Title of the passing sonar plot} 15 | } 16 | \value{ 17 | a ggplot2 object 18 | } 19 | \description{ 20 | This function allows for data, that can be from Opta or Statsbomb, to be used 21 | for plotting pass sonars. 22 | } 23 | \examples{ 24 | \dontrun{ 25 | plot <- plot_sonar(data, data_type = "statsbomb") 26 | plot 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /man/plot_timeline.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/plot_timeline.r 3 | \name{plot_timeline} 4 | \alias{plot_timeline} 5 | \title{Create timeline charts (xG) using 'understat' event data.} 6 | \usage{ 7 | plot_timeline( 8 | data, 9 | match_year, 10 | team_home, 11 | team_away, 12 | color_home, 13 | color_away, 14 | theme = "" 15 | ) 16 | } 17 | \arguments{ 18 | \item{data}{Data, for now only compatible with understat data.} 19 | 20 | \item{match_year}{the year the match was played.} 21 | 22 | \item{team_home}{home team according to data.} 23 | 24 | \item{team_away}{away team according to data.} 25 | 26 | \item{color_home}{line colour for the home team.} 27 | 28 | \item{color_away}{line colour for the away team.} 29 | 30 | \item{theme}{Choose from 4 ggplot2 themes -> dark, almond, rose, white.} 31 | } 32 | \value{ 33 | ggplot2 object of a xG timeline plot 34 | } 35 | \description{ 36 | This function allows to make match timelines using data collected event-by-event. 37 | } 38 | \examples{ 39 | \dontrun{ 40 | plot <- plot_timeline(data = data, match_year = 2021, 41 | team_home = "Manchester United", team_away = "Liverpool", 42 | color_home = "#e31a1c", color_away = "#980043", theme = "dark") 43 | plot 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /man/plot_trendline.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/plot_trendline.R 3 | \name{plot_trendline} 4 | \alias{plot_trendline} 5 | \title{Function for plotting xG Trendline with FBref/ StatsBomb data.} 6 | \usage{ 7 | plot_trendline(data, team, color_xg, color_xga, rolling_average, theme = "") 8 | } 9 | \arguments{ 10 | \item{data}{is for the dataset used. Select the number of matches wanted in the viz beforehand.} 11 | 12 | \item{team}{is to select the specific team for the viz. Team must be accurate as per FBref specifications.} 13 | 14 | \item{color_xg}{is for selecting color for xGoals.} 15 | 16 | \item{color_xga}{is for selecting the color for xGoalsAgainst.} 17 | 18 | \item{rolling_average}{is for setting the rolling average for the data.} 19 | 20 | \item{theme}{to select the theme from 4 options -> dark, almond, rose, white.} 21 | } 22 | \description{ 23 | The data can be scraped from FBref.\cr 24 | Dataframe passed in must have the following column names: \cr 25 | \cr 26 | Date (format: year-month-day).yyyy-mm-dd, \cr 27 | Home_xG (xG for Home Team), \cr 28 | Away_xG (xG for Away Team), \cr 29 | Home (Home Team), \cr 30 | Away (Away Team) 31 | } 32 | \details{ 33 | For best clarity, export plot as a 2000x1000 png 34 | } 35 | \examples{ 36 | \dontrun{ 37 | plot <- plot_trendline(data = pl, team = "Tottenham", 38 | color_xg = "#08519c", color_xga = "#cb181d", 39 | rolling_average = 10, theme = "dark") 40 | plot 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /man/plot_voronoi.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/plot_voronoi.R 3 | \name{plot_voronoi} 4 | \alias{plot_voronoi} 5 | \title{Function for plotting voronoi diagrams on the pitch} 6 | \usage{ 7 | plot_voronoi( 8 | data, 9 | data_type = "statsbomb", 10 | colour = "#E74C3C", 11 | fill = "", 12 | alpha = 0.4, 13 | title = "", 14 | theme = "dark" 15 | ) 16 | } 17 | \arguments{ 18 | \item{data}{Dataframe that houses pass data. Opta/Statsbomb dataframe must contain atleast the following columns: \code{x}, \code{y}} 19 | 20 | \item{data_type}{Type of data that is being put in: opta or statsbomb. Default set to "statsbomb".} 21 | 22 | \item{colour}{The colour of the points in the voronoi plot.} 23 | 24 | \item{fill}{Name of column to add a fill component to the plot} 25 | 26 | \item{alpha}{Alpha value for opacity of fill. Default set to 0.4} 27 | 28 | \item{title}{Title of the plot.} 29 | 30 | \item{theme}{Indicates what theme the map must be shown in: dark (default), white, rose, almond.} 31 | } 32 | \value{ 33 | a ggplot2 object 34 | } 35 | \description{ 36 | The function allows for creation of voronoi diagrams on a football pitch 37 | with either Opta or Statsbomb data. 38 | } 39 | \examples{ 40 | \dontrun{ 41 | plot <- plot_voronoi(data = data, colour = "blue", title = "Team 1") 42 | plot 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /man/shift_column.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{shift_column} 4 | \alias{shift_column} 5 | \title{shift_column} 6 | \usage{ 7 | shift_column( 8 | data, 9 | columns, 10 | new_names = sprintf("\%s.Shifted", columns), 11 | len = 1L, 12 | up = TRUE 13 | ) 14 | } 15 | \arguments{ 16 | \item{data}{data} 17 | 18 | \item{columns}{columns to shift over} 19 | 20 | \item{new_names}{New name of shifted columns, Default: sprintf("\%s.Shifted", columns)} 21 | 22 | \item{len}{length of rows to shift, Default: 1} 23 | 24 | \item{up}{shift rows up (TRUE) or down (FALSE), Default: TRUE} 25 | } 26 | \value{ 27 | data.frame with shifted columns 28 | } 29 | \description{ 30 | Port of \code{shift.column()} from Jared Lander's {useful} package 31 | with a few minor changes. 32 | } 33 | \keyword{internal} 34 | -------------------------------------------------------------------------------- /man/text_wrap.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{text_wrap} 4 | \alias{text_wrap} 5 | \title{text_wrap} 6 | \usage{ 7 | text_wrap(x) 8 | } 9 | \arguments{ 10 | \item{x}{text vector} 11 | } 12 | \value{ 13 | text vector 14 | } 15 | \description{ 16 | Wrap text using stringi to mimic stringr 17 | } 18 | \keyword{internal} 19 | -------------------------------------------------------------------------------- /man/viridis_d_pal.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \docType{data} 4 | \name{viridis_d_pal} 5 | \alias{viridis_d_pal} 6 | \title{viridis_d_pal} 7 | \format{ 8 | An object of class \code{character} of length 256. 9 | } 10 | \usage{ 11 | viridis_d_pal 12 | } 13 | \description{ 14 | Viridis palette hex codes. Used in \code{plot_passflow()} function. 15 | Created by Stefan van der Walt & Nathaniel Smith for matplotlib. 16 | } 17 | \keyword{internal} 18 | -------------------------------------------------------------------------------- /man/xTGrid.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/threat_guide.R 3 | \docType{data} 4 | \name{xTGrid} 5 | \alias{xTGrid} 6 | \title{xT calculation data for analysis} 7 | \format{ 8 | A \code{dataframe} with 12 columns and 8 rows spanning 9 | a football pitch. 10 | } 11 | \usage{ 12 | xTGrid 13 | } 14 | \description{ 15 | a simple \code{dataframe} that is used for 16 | calculating expected threat values 17 | } 18 | \keyword{datasets} 19 | -------------------------------------------------------------------------------- /man/zissou_pal.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \docType{data} 4 | \name{zissou_pal} 5 | \alias{zissou_pal} 6 | \title{title zissou_pal} 7 | \format{ 8 | An object of class \code{character} of length 10. 9 | } 10 | \usage{ 11 | zissou_pal 12 | } 13 | \description{ 14 | 'Zissou' palette hex codes. Useed in \code{plot_sonar()} function. 15 | Created by Karthik Ram in the {wesanderson} R package. 16 | } 17 | \keyword{internal} 18 | -------------------------------------------------------------------------------- /pkgdown/extra.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Titillium+Web&display=swap'); 2 | @import url("https://cdn.rawgit.com/tonsky/FiraCode/1.205/distr/fira_code.css"); 3 | 4 | body, .navbar-inverse { 5 | font-family: 'Titillium Web', sans-serif; 6 | font-weight: 400; 7 | } 8 | 9 | h1, h2, h3, h4, .h1, .h2, .h3, .h4 { 10 | font-family: 'Titillium Web Bold', sans-serif; 11 | font-weight: 700; 12 | } 13 | 14 | pre, code { 15 | font-family: "Fira Code", Consolas, Inconsolata, monospace; 16 | } 17 | 18 | .label-default { 19 | background-color: transparent; 20 | } 21 | 22 | .navbar-inverse { 23 | border-color: transparent; 24 | min-height: 50px; 25 | } 26 | 27 | .navbar-brand { 28 | background-image: url(logo.png); 29 | background-size: 32px auto; 30 | background-repeat: no-repeat; 31 | background-position: 15px center; 32 | padding: 0 0 0 54px; 33 | height: 50px; 34 | line-height: 50px; 35 | } 36 | 37 | .template-home img.logo, img.logo { 38 | height: 155px; 39 | width: 140px; 40 | } 41 | -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/pkgdown/favicon/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/pkgdown/favicon/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/pkgdown/favicon/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/pkgdown/favicon/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/pkgdown/favicon/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/pkgdown/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/pkgdown/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/pkgdown/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhiamishra/ggshakeR/51b7c2df7c691b3d386f463f18ffd0b152097a36/pkgdown/favicon/favicon.ico -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(ggshakeR) 3 | 4 | test_check("ggshakeR") 5 | -------------------------------------------------------------------------------- /vignettes/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | *.R 3 | -------------------------------------------------------------------------------- /vignettes/Guide_to_EPV.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Guide to Expected Possession Value" 3 | author: "Abhishek A. Mishra" 4 | date: "`r Sys.Date()`" 5 | output: rmarkdown::html_vignette 6 | vignette: > 7 | %\VignetteIndexEntry{Guide to Expected Possession Value} 8 | %\VignetteEngine{knitr::rmarkdown} 9 | %\VignetteEncoding{UTF-8} 10 | --- 11 | 12 | ```{r, include = FALSE} 13 | knitr::opts_chunk$set( 14 | collapse = TRUE, 15 | comment = "#>", 16 | eval = FALSE 17 | ) 18 | ``` 19 | 20 | Welcome to the Expected Possession Value Guide for ggshakeR! 21 | 22 | EPV - short for Expected Possession Value - is one of the most 23 | popular and informative metrics out there! Measuring the probability that an action leads to a goal, EPV allows you to 24 | objectively calculate passes, carries, and other events. 25 | 26 | EPV was developed by [Javier Fernández, Luke Bornn & Daniel Cervone](https://link.springer.com/article/10.1007/s10994-021-05989-6). 27 | 28 | There are various iterations of EPV that exist out there. 29 | OptaPro has their own PV while StatsBomb has something similar with their OBV. 30 | 31 | The only publicly available data regarding EPV comes from Laurie Shaw who made an EPV grid public with his work with [Friends of Tracking](https://www.youtube.com/watch?v=KXSLKwADXKI&feature=emb_title) 32 | 33 | Laurie Shaw's EPV grid is what ggshakeR uses. 34 | 35 | Every time you load in the package, ggshakeR will load in a dataframe called __EPVGrid__. __EPVGrid__ houses all the EPV data that Laurie Shaw made public and now it is available for you in a line of code! 36 | 37 | Simply write: 38 | 39 | ```{r, eval=FALSE} 40 | library(ggshakeR) 41 | 42 | head(EPVGrid) 43 | ``` 44 | 45 | 46 | This dataframe is there for you to use and play with. The columns correspond to pitch divisions from your own goal (V1) to the opponent's goal (V50). The grid is symmetrical in half and as such, there is no "top"/"bottom" of the pitch. 47 | 48 | However, what if you want to use EPV? Calculate it? 49 | 50 | ggshakeR offers you the `calculate_epv()` function that calculates EPV for the data that is passed in! 51 | 52 | Here are some characteristics: 53 | 54 | * Needs 4 columns named as `x`, `y`, `finalX`, `finalY`. 55 | 56 | Here's the cool thing about the `calculate_epv()` function: __It calculates the xT of the start of a pass/carry and the xT of the end of the pass/carry__. 57 | 58 | Why is this important? Well, let's take a take-on. That only has a starting `x` ,`y` . In the dataframe, the columns of `finalX` and `finalY` have `NAs` in them. The `calculate_epv()` will give you the EPV for both the starting and ending locations allowing you to give a value to single-event values. 59 | 60 | This means, you can use this function beyond passes and carries and be able to calculate EPV for clearances, tackles, take-ons, etc! In addition, by separating out the EPV values for the start and end, you can apply more advanced analytics to the passes. 61 | 62 | Let's see how we can use it! 63 | 64 | ## Using the `calculate_epv()` function 65 | 66 | First, let's get some data! You can either import you data or use [Statsbomb's open free dataset](https://github.com/statsbomb/open-data) 67 | 68 | In this example, I'll be using StatsBomb's [Messi Data](https://statsbomb.com/2019/12/messi-data-biography-15-seasons-now-complete-and-available/) for La Liga 2014/15: 69 | 70 | ```{r, eval=FALSE} 71 | library(StatsBombR) 72 | 73 | Comp <- FreeCompetitions() %>% 74 | filter(competition_id == 11 & season_name == "2014/2015") 75 | Matches <- FreeMatches(Comp) 76 | StatsBombData <- free_allevents(MatchesDF = Matches, Parallel = TRUE) 77 | StatsBombData <- allclean(StatsBombData) 78 | 79 | plottingData <- StatsBombData 80 | ``` 81 | 82 | Before plotting, I am going to rename my columns: 83 | 84 | ```{r, eval=FALSE} 85 | plottingData <- plottingData %>% 86 | rename("x" = "location.x", 87 | "y" = "location.y", 88 | "finalX" = "pass.end_location.x", 89 | "finalY" = "pass.end_location.y") 90 | ``` 91 | 92 | For `calculate_epv()`, __always write the `type` that the data is: opta or statsbomb__. After that, we simply get: 93 | 94 | ```{r, eval=FALSE} 95 | EPVData <- calculate_epv(plottingData, type = "statsbomb") 96 | ``` 97 | 98 | If we inspect `EPVData`, we see these two new columns at the very end: 99 | 100 | ![calculate_epv() producing EPVStart and EPVEnd](../man/figures/calculate_epv.png){width=90%} 101 | 102 | As you'll see, `calculate_epv()` will put a -1 in areas where there was a value that was not right for calculation. To calculate the difference in EPV, simply write: 103 | 104 | ```{r, eval=FALSE} 105 | EPVData <- EPVData %>% 106 | mutate(EPV = EPVEnd - EPVStart) 107 | ``` 108 | 109 | This will give you the difference in EPV and this value can be used for passes, carries - anything that has a start and an end location. Use `EPVStart`/`EPVEnd` for single-occuring events such as take-ons, tackles, if you so wish. 110 | -------------------------------------------------------------------------------- /vignettes/Guide_to_Exp_Threat.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Guide to Expected Threat" 3 | author: "Abhishek A. Mishra" 4 | date: "`r Sys.Date()`" 5 | output: rmarkdown::html_vignette 6 | vignette: > 7 | %\VignetteIndexEntry{Guide to Expected Threat} 8 | %\VignetteEngine{knitr::rmarkdown} 9 | %\VignetteEncoding{UTF-8} 10 | --- 11 | 12 | ```{r, include = FALSE} 13 | knitr::opts_chunk$set( 14 | collapse = TRUE, 15 | comment = "#>", 16 | eval = FALSE 17 | ) 18 | ``` 19 | 20 | Welcome to the Expected Threat Guide for ggshakeR! 21 | 22 | First things first, we are only able to do this because of the amazing work by Karun Singh. Read his excellent [xT blog post](https://karun.in/blog/expected-threat.html) that goes into in-depth explanation of the need and use of the metric. 23 | 24 | Karun Singh's xT grid is what ggshakeR uses. Every time you load in the package, ggshakeR will load in a dataframe called __xTGrid__. __xTGrid__ houses all the xT data that Karun Singh made public and now it is available for you in a line of code! 25 | 26 | Simply write: 27 | 28 | ```{r} 29 | library(ggshakeR) 30 | 31 | head(xTGrid) 32 | ``` 33 | 34 | 35 | This dataframe is there for you to use and play with. The columns correspond to pitch divisions from your own goal (X0) to the opponent's goal (X11). The grid is symmetrical in half and as such, there is no "top"/"bottom" of the pitch. 36 | 37 | However, what if you want to use xT? Calculate it? 38 | 39 | ggshakeR offers you the `calculate_threat()` function that calculates xT for the data that is passed in! 40 | 41 | Here are some characteristics: 42 | 43 | * Needs 4 columns named as `x`, `y`, `finalX`, `finalY`. 44 | 45 | Here's the cool thing about the `calculate_threat()` function: __It calculates the xT of the start of a pass/carry and the xT of the end of the pass/carry__. 46 | 47 | Why is this important? Well, let's take a take-on. That only has a starting `x` ,`y` . In the dataframe, the columns of `finalX` and `finalY` have `NAs` in them. The `calculate_threat()` will give you the xT for both the starting and ending locations allowing you to give a value to single-event values - basically, it gives you the most freedom and information regarding xT. 48 | 49 | Let's see how we can use it! 50 | 51 | ## Using the `calculate_threat()` function 52 | 53 | First, let's get some data! You can either import you data or use [Statsbomb's open free dataset](https://github.com/statsbomb/open-data) 54 | 55 | In this example, I'll be using StatsBomb's [Messi Data](https://statsbomb.com/2019/12/messi-data-biography-15-seasons-now-complete-and-available/) for La Liga 2014/15: 56 | 57 | ```{r, eval=FALSE} 58 | library(StatsBombR) 59 | 60 | Comp <- FreeCompetitions() %>% 61 | filter(competition_id == 11 & season_name == "2014/2015") 62 | Matches <- FreeMatches(Comp) 63 | StatsBombData <- free_allevents(MatchesDF = Matches, Parallel = TRUE) 64 | StatsBombData <- allclean(StatsBombData) 65 | 66 | plottingData <- StatsBombData 67 | ``` 68 | 69 | Before plotting, I am going to rename my columns: 70 | 71 | ```{r, eval=FALSE} 72 | plottingData <- plottingData %>% 73 | rename("x" = "location.x", 74 | "y" = "location.y", 75 | "finalX" = "pass.end_location.x", 76 | "finalY" = "pass.end_location.y") 77 | ``` 78 | 79 | For `calculate_threat()`, __always write the type that the data is: opta or statsbomb__. After that, we simply get: 80 | 81 | ```{r, eval=FALSE} 82 | ## NOTE: from version 0.2.0 ALL function arguments are now in snake_case 83 | ## 'dataType' is now 'data_type', etc. 84 | xTData <- calculate_threat(data = plottingData, type = "statsbomb") 85 | ``` 86 | 87 | If we inspect `xTData`, we see these two new columns at the very end: 88 | 89 | ![calculate_threat() producing xTStart and xTEnd](../man/figures/calculate_threat.png){width=90%} 90 | 91 | As you'll see, `calculate_threat()` will put a -1 in areas where there was a value that was not right for calculation. To calculate the difference in xT, simply write: 92 | 93 | ```{r, eval=FALSE} 94 | xTData <- xTData %>% 95 | mutate(xT = xTEnd - xTStart) 96 | ``` 97 | 98 | This will give you the difference in xT and this value can be used for passes, carries - anything that has a start and an end location. Use `xTStart`/`xTEn`d for single-occuring events such as take-ons, tackles, if you so wish. 99 | -------------------------------------------------------------------------------- /vignettes/Guide_to_Version_0-2-0.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Guide to function argument changes in Version 0.2.0" 3 | author: "Ryo Nakagawara" 4 | date: "`r Sys.Date()`" 5 | output: rmarkdown::html_vignette 6 | vignette: > 7 | %\VignetteIndexEntry{Guide to function argument changes in Version 0.2.0} 8 | %\VignetteEngine{knitr::rmarkdown} 9 | %\VignetteEncoding{UTF-8} 10 | --- 11 | 12 | ```{r, include = FALSE} 13 | knitr::opts_chunk$set( 14 | collapse = TRUE, 15 | comment = "#>", 16 | eval = FALSE 17 | ) 18 | ``` 19 | 20 | Along side the new functionality in the `v0.2.0` update, there have been a lot of changes made to existing {ggshakeR} functions as well. This is regarding the consistency and standardization of argument names in the package. 21 | 22 | For guides on new functions please see: 23 | 24 | * [Guide to Pitch Plots](https://abhiamishra.github.io/ggshakeR/articles/Guide_to_Pitch_Plots.html) 25 | * [Guide to Creating Pizza Plots](https://abhiamishra.github.io/ggshakeR/articles/Guide_to_PizzaPlots.html) 26 | * [Guide to Expected Threat](https://abhiamishra.github.io/ggshakeR/articles/Guide_to_Exp_Threat.html) 27 | 28 | ```{r setup} 29 | library(ggshakeR) 30 | ``` 31 | 32 | The problem that really kicked off these changes was the fact that as I was looking through the package's code base, I found that we had functions with arguments using `snake_case` in some functions, others using `camelCase`, and worst of all some functions that used both `snake_case` __AND__ `camelCase` in the argument names. 33 | 34 | ```{r example-bad} 35 | plot_passflow(pass_data, dataType, bin_size) # !!! (╯°□°)╯︵ ┻━┻ 36 | ``` 37 | 38 | After some discussion with the other authors (`Abhishek` & `Harsh`), I have standardized everything to `snake_case` as well as making a number of other changes to argument names for the sake of __consistency__. This does mean some pain in the short-term, but please do know these changes are done with user experience in mind in the long-term by lessening the cognitive load for using the functions in the package. Some argument names are longer now, but this shouldn't be an issue as in RStudio you can simply press `Tab` while typing to auto-complete longer argument names (other modern IDEs should have a similar feature as well). An exception (hopefully a rare one as we go forward) is `binwidth` as this is essentially an argument that is being passed on to some underlying `ggplot2` function calls and we wanted to preserve the same argument name for familiarity's sake. 39 | 40 | The `type` arguments have also been rejigged so that the **first** `type` argument will always be the `type` related to the function. So for example each `plot_*()` function's `type` argument will specify the **plot** type, then other arguments for the `type` of other things will have their own prefix such as `data_type` and so on. Another QOL change was to make the `data` argument be standardized across **all** functions so you don't have to figure out whether it's `event_data` or `pass_data` or `sonarData` or even simply `data`... it's __all__ just `data` now. 41 | 42 | Some arguments have changed their order in the functions, to learn about why function order is important, check out [Function arguments section in 'Advanced R'](http://adv-r.had.co.nz/Functions.html#function-arguments). The functions in {ggshakeR} follows R function conventions by having the `data` argument come first, then usually a `type` argument, then the rest in an order we think makes sense. 43 | 44 | Listed below are the changes made for version `0.2.0` to previously existing functions: 45 | 46 | ## `calculate_threat()` 47 | 48 | ```{r} 49 | calculate_threat(data, type) 50 | ``` 51 | 52 | - `data`: changed from `event_data` 53 | - `type`: changed from `dataType` 54 | 55 | ## `plot_heatmap()` 56 | 57 | ```{r} 58 | plot_heatmap(data, type, data_type, binwidth, theme) 59 | ``` 60 | 61 | * `data`: changed from `event_data` 62 | * `type`: specifically refers to the **plot** type while `data_type` (below) allows you to specify the data type (`StatsBomb`, `Opta`, etc.) 63 | * `data_type`: changed from `dataType` 64 | * `binwidth`: changed from `bin`, now the same argument name as the underlying call to `geom_bin2d()` 65 | * `theme`: moved to be the last argument 66 | 67 | ## `plot_pass()` 68 | 69 | ```{r} 70 | plot_pass(data, 71 | type, progressive_pass, 72 | cross, shot, switch, outcome, 73 | theme) 74 | ``` 75 | 76 | * `data`: changed from `pass_data` 77 | * `progressive_pass`: changed from `prog` 78 | * `type`: changed from `plot_type`, specifically refers to **plot** type as it is a `plot_*()` function 79 | * `data_type`: changed from `dataType` 80 | 81 | ## `plot_passflow()` 82 | 83 | ```{r} 84 | plot_passflow(data, data_type, binwidth) 85 | ``` 86 | 87 | * `data`: changed from `pass_data` 88 | * `data_type`: moved to come after the `data` argument 89 | * `binwidth`: changed from `bin_size`, now the same argument name as the underlying call to `geom_bin2d()` 90 | 91 | ## `plot_pizza()` 92 | 93 | ```{r} 94 | plot_pizza(data, type, template, 95 | color_possession, color_attack, color_defense, color_compare, 96 | player_1, player_2, 97 | season, season_player_1, season_player_2, 98 | theme) 99 | ``` 100 | 101 | * `color_possession`: changed from `color_poss` 102 | * `color_attack`: changed from `color_att` 103 | * `color_defense`: changed from `color_def` 104 | 105 | ## `plot_scatter()` 106 | 107 | ```{r} 108 | plot_scatter(data, x, y, label, 109 | set_size_num, set_size_var, 110 | set_color_num, set_color_var, 111 | title, title_size, 112 | subtitle, subtitle_size, 113 | caption, caption_size, 114 | theme) 115 | ``` 116 | 117 | * `x`: changed from `scatter_x` 118 | * `y`: changed from `scatter_y` 119 | * `label`: changed from `scatter_label` 120 | * `title`: changed from `scatter_title` 121 | * `subtitle`: changed from `scatter_subtitle` 122 | * `subtitle_size`: changed from `subt_size` 123 | * `caption`: changed from `scatter_cap` 124 | * `caption_size`: changed from `cap_size` 125 | * `theme`: moved to the very end of the argument order 126 | 127 | ## `plot_shot()` 128 | 129 | ```{r} 130 | plot_shot(data, type, bins, highlight_goals, average_location) 131 | ``` 132 | 133 | * `bins`: changed from `bin_size`, now the same argument name as the underlying call to `geom_hex()` 134 | * `average_location`: changed from `avg_loc` 135 | 136 | ## `plot_sonar()` 137 | 138 | ```{r} 139 | plot_sonar(data, data_type, title) 140 | ``` 141 | 142 | * `data`: changed from `sonarData` 143 | 144 | ## `plot_trendline()` 145 | 146 | ```{r} 147 | plot_trendline(data, team, color_xg, color_xga, rolling_average, theme) 148 | ``` 149 | 150 | * `color_xg`: changed from `colour_xg` 151 | * `color_xga`: changed from `colour_xga` 152 | * `rolling_average`: changed from `roll_avg` 153 | 154 | ## `plot_timeline()` 155 | 156 | ```{r} 157 | plot_timeline(data, match_year, 158 | team_home, team_away, 159 | color_home, color_away, theme) 160 | ``` 161 | 162 | * `color_home`: changed from `home_color` 163 | * `color_away`: changed from `away_color` 164 | 165 | ## Installing previous versions of the package 166 | 167 | For those who are not ready to fully commit to these changes (because you have a large script with {ggshakeR} functions or whatever) but have accidentally installed the new version, fear not as you can go back and install a prior version of the package. 168 | 169 | ```{r} 170 | ## Install previous 0.1.2 version 171 | devtools::install_github("abhiamishra/ggshakeR@0.1.2") 172 | ``` 173 | 174 | Do note that the changes listed in this vignette are permanent and will be the standard going forward. So please take the time to read this vignette and the documentation carefully so that you can transition over to the new argument syntax and make use of the new functionality that version 0.2.0 (and beyond!) provides. 175 | --------------------------------------------------------------------------------