Skip to content

Commit 5cd7bcf

Browse files
Merge pull request vuejs#6 from American-Soccer-Analysis/r-tests
ASA-159: Write tests and fix bugs
2 parents fc226fc + 544c2e5 commit 5cd7bcf

21 files changed

+2569
-54
lines changed

R-package/.Rbuildignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@
44
^\.Rproj\.user$
55
^\.github$
66
^LICENSE\.md$
7+
^_pkgdown\.yml$
8+
^docs$
9+
^pkgdown$

R-package/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
docs

R-package/DESCRIPTION

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
Package: itscalledsoccer
2-
Title: What the Package Does (Title Case)
2+
Title: American Soccer Analysis API Client
33
Version: 0.0.0.9000
44
Author: Tyler Richardett <[email protected]>
55
Maintainer: Tyler Richardett <[email protected]>
@@ -21,7 +21,8 @@ Imports:
2121
R6 (>= 2.5.0),
2222
tidyr (>= 1.1.1),
2323
stringi (>= 1.5.3),
24-
rlang (>= 0.4.10)
24+
rlang (>= 0.4.10),
25+
data.table (>= 1.13.0)
2526
Suggests:
2627
testthat (>= 3.0.0)
2728
Config/testthat/edition: 3

R-package/NAMESPACE

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@
22

33
export("%>%")
44
export(AmericanSoccerAnalysis)
5+
importFrom(R6,R6Class)
56
importFrom(magrittr,"%>%")
7+
importFrom(rlang,.data)

R-package/R/client.R

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ AmericanSoccerAnalysis <- R6::R6Class("AmericanSoccerAnalysis",
1313
#' @field LEAGUES List of stylized league names.
1414
LEAGUES = c("nwsl", "mls", "uslc", "usl1", "nasl"),
1515

16-
#' @field BASE_URL API base URL.
17-
BASE_URL = NULL,
16+
#' @field base_url API base URL.
17+
base_url = NULL,
1818

1919
#' @field players Data frame containing players from all leagues.
2020
players = NULL,
@@ -31,23 +31,39 @@ AmericanSoccerAnalysis <- R6::R6Class("AmericanSoccerAnalysis",
3131
#' @field referees Data frame containing referees from all leagues.
3232
referees = NULL,
3333

34+
#' @field httr_configs Configs to pass on to all \code{httr} functions. See \href{https://www.rdocumentation.org/packages/httr/versions/1.4.2/topics/config}{documentation}.
35+
httr_configs = list(),
36+
3437
#' @description Creates a new \code{AmericanSoccerAnalysis} object.
38+
#' @param ... Configs to pass on to all \code{httr} functions. See \href{https://www.rdocumentation.org/packages/httr/versions/1.4.2/topics/config}{documentation}.
3539
#' @return A new \code{AmericanSoccerAnalysis} object.
36-
initialize = function() {
37-
self$BASE_URL <- glue::glue("https://app.americansocceranalysis.com/api/{self$API_VERSION}")
40+
initialize = function(...) {
41+
self$base_url <- glue::glue("https://app.americansocceranalysis.com/api/{self$API_VERSION}")
42+
self$httr_configs <- list(...)
3843
self$players <- get_entity(self, "player")
3944
self$teams <- get_entity(self, "team")
4045
self$stadia <- get_entity(self, "stadium")
4146
self$managers <- get_entity(self, "manager")
4247
self$referees <- get_entity(self, "referee")
4348
},
4449

50+
#' @description Appends new \code{httr} configs to the existing class.
51+
#' @param ... Configs to pass on to all \code{httr} functions. See \href{https://www.rdocumentation.org/packages/httr/versions/1.4.2/topics/config}{documentation}.
52+
add_httr_configs = function(...) {
53+
self$httr_configs <- c(self$httr_configs, list(...))
54+
},
55+
56+
#' @description Removes all \code{httr} configs from the existing class.
57+
reset_httr_configs = function() {
58+
self$httr_configs <- list()
59+
},
60+
4561
#' @description Retrieves a data frame containing player names, IDs, and other metadata.
4662
#' @param leagues Leagues on which to filter. Accepts a character vector of length >= 1.
4763
#' @param ids Player IDs on which to filter. Cannot be combined with \code{names}. Accepts a character vector of length >= 1.
4864
#' @param names Player names on which to filter. Partial matches are accepted. Cannot be combined with \code{ids}. Accepts a character vector of length >= 1.
4965
get_players = function(leagues, ids, names) {
50-
players <- filter_entity(self$players, self$LEAGUES, leagues, ids, names)
66+
players <- filter_entity(self, "players", leagues, ids, names)
5167
return(players)
5268
},
5369

@@ -56,7 +72,7 @@ AmericanSoccerAnalysis <- R6::R6Class("AmericanSoccerAnalysis",
5672
#' @param ids Team IDs on which to filter. Cannot be combined with \code{names}. Accepts a character vector of length >= 1.
5773
#' @param names Team names on which to filter. Partial matches and abbreviations are accepted. Cannot be combined with \code{ids}. Accepts a character vector of length >= 1.
5874
get_teams = function(leagues, ids, names) {
59-
teams <- filter_entity(self$teams, self$LEAGUES, leagues, ids, names)
75+
teams <- filter_entity(self, "teams", leagues, ids, names)
6076
return(teams)
6177
},
6278

@@ -65,7 +81,7 @@ AmericanSoccerAnalysis <- R6::R6Class("AmericanSoccerAnalysis",
6581
#' @param ids Stadium IDs on which to filter. Cannot be combined with \code{names}. Accepts a character vector of length >= 1.
6682
#' @param names Stadium names on which to filter. Partial matches are accepted. Cannot be combined with \code{ids}. Accepts a character vector of length >= 1.
6783
get_stadia = function(leagues, ids, names) {
68-
stadia <- filter_entity(self$stadia, self$LEAGUES, leagues, ids, names)
84+
stadia <- filter_entity(self, "stadia", leagues, ids, names)
6985
return(stadia)
7086
},
7187

@@ -74,7 +90,7 @@ AmericanSoccerAnalysis <- R6::R6Class("AmericanSoccerAnalysis",
7490
#' @param ids Manager IDs on which to filter. Cannot be combined with \code{names}. Accepts a character vector of length >= 1.
7591
#' @param names Manager names on which to filter. Partial matches are accepted. Cannot be combined with \code{ids}. Accepts a character vector of length >= 1.
7692
get_managers = function(leagues, ids, names) {
77-
managers <- filter_entity(self$managers, self$LEAGUES, leagues, ids, names)
93+
managers <- filter_entity(self, "managers", leagues, ids, names)
7894
return(managers)
7995
},
8096

@@ -83,7 +99,7 @@ AmericanSoccerAnalysis <- R6::R6Class("AmericanSoccerAnalysis",
8399
#' @param ids Referee IDs on which to filter. Cannot be combined with \code{names}. Accepts a character vector of length >= 1.
84100
#' @param names Referee names on which to filter. Partial matches are accepted. Cannot be combined with \code{ids}. Accepts a character vector of length >= 1.
85101
get_referees = function(leagues, ids, names) {
86-
referees <- filter_entity(self$referees, self$LEAGUES, leagues, ids, names)
102+
referees <- filter_entity(self, "referees", leagues, ids, names)
87103
return(referees)
88104
},
89105

R-package/R/entities.R

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,42 @@
1+
#' @importFrom rlang .data
12
get_entity <- function(self, type) {
2-
entity_all <- data.frame()
3+
entity_all <- list()
4+
i <- 1
35

46
for (league in self$LEAGUES) {
57
if (type == "stadium") {
6-
url <- glue::glue("{self$BASE_URL}/{league}/stadia")
8+
url <- glue::glue("{self$base_url}/{league}/stadia")
79
} else {
8-
url <- glue::glue("{self$BASE_URL}/{league}/{type}s")
10+
url <- glue::glue("{self$base_url}/{league}/{type}s")
911
}
1012

1113
response <- .execute_query(self, url)
1214
response <- response %>% dplyr::mutate(competition = league)
1315

14-
entity_all <- entity_all %>% dplyr::bind_rows(response)
16+
entity_all[[i]] <- response
17+
i <- i + 1
1518
}
1619

17-
entity_all <- entity_all %>%
20+
entity_all <- data.table::rbindlist(entity_all, fill = TRUE) %>%
1821
dplyr::group_by(dplyr::across(c(-dplyr::matches("competition"), -dplyr::starts_with("season"), -dplyr::ends_with("position")))) %>%
19-
dplyr::summarize(competitions = list(rlang::.data$competition)) %>%
22+
dplyr::summarize(competitions = list(.data$competition)) %>%
2023
dplyr::ungroup() %>%
2124
dplyr::arrange(!!as.symbol(glue::glue("{type}_name")))
2225

2326
return(entity_all)
2427
}
2528

26-
filter_entity <- function(entity_all, league_options, leagues, ids, names) {
27-
.check_leagues(leagues, league_options)
29+
#' @importFrom rlang .data
30+
filter_entity <- function(self, entity, leagues, ids, names) {
31+
.check_leagues(self, leagues)
2832
.check_ids_names(ids, names)
2933

30-
entity_filtered <- entity_all %>%
31-
tidyr::unnest(rlang::.data$competitions)
34+
entity_filtered <- self[[entity]] %>%
35+
tidyr::unnest(.data$competitions)
3236

3337
if (!missing(leagues)) {
3438
entity_filtered <- entity_filtered %>%
35-
dplyr::filter(rlang::.data$competitions %in% leagues)
39+
dplyr::filter(.data$competitions %in% leagues)
3640
}
3741

3842
if (!missing(names)) {
@@ -45,14 +49,15 @@ filter_entity <- function(entity_all, league_options, leagues, ids, names) {
4549
}
4650

4751
entity_filtered <- entity_filtered %>%
48-
dplyr::select(-rlang::.data$competitions) %>%
52+
dplyr::select(-.data$competitions) %>%
4953
dplyr::distinct()
5054

5155
return(entity_filtered)
5256
}
5357

58+
#' @importFrom rlang .data
5459
get_games <- function(self, leagues, game_ids, team_ids, team_names, seasons, stages) {
55-
.check_leagues(leagues, self$LEAGUES)
60+
.check_leagues(self, leagues)
5661
.check_ids_names(team_ids, team_names)
5762

5863
if (missing(leagues)) leagues <- self$LEAGUES
@@ -64,17 +69,18 @@ get_games <- function(self, leagues, game_ids, team_ids, team_names, seasons, st
6469
if (!missing(seasons)) query$season_name <- seasons
6570
if (!missing(stages)) query$stage_name <- stages
6671

67-
games <- data.frame()
72+
games <- list()
73+
i <- 1
6874

6975
for (league in leagues) {
70-
url <- glue::glue("{self$BASE_URL}/{league}/games")
76+
url <- glue::glue("{self$base_url}/{league}/games")
7177

7278
response <- .execute_query(self, url, query)
7379

74-
games <- games %>%
75-
dplyr::bind_rows(response) %>%
76-
dplyr::arrange(rlang::.data$date_time_utc)
80+
games[[i]] <- response
81+
i <- i + 1
7782
}
7883

84+
games <- data.table::rbindlist(games, fill = TRUE) %>% dplyr::arrange(.data$date_time_utc)
7985
return(games)
8086
}

R-package/R/imports.R

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,7 @@
1212
#' @param rhs A function call using the magrittr semantics.
1313
#' @return The result of calling `rhs(lhs)`.
1414
NULL
15+
16+
# Suppress R CMD check note
17+
#' @importFrom R6 R6Class
18+
NULL

R-package/R/stats.R

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#' @importFrom rlang .data
12
get_stats <- function(self, type, entity, leagues, ...) {
23
query <- list(...)
34
if (!all(rlang::have_name(query))) {
@@ -6,7 +7,7 @@ get_stats <- function(self, type, entity, leagues, ...) {
67
stop(msg)
78
}
89

9-
.check_leagues(leagues, self$LEAGUES)
10+
.check_leagues(self, leagues)
1011
if (missing(leagues)) leagues <- self$LEAGUES
1112

1213
if (sum(grepl("player_", names(query))) > 0) {
@@ -42,24 +43,29 @@ get_stats <- function(self, type, entity, leagues, ...) {
4243
}
4344

4445

45-
stats <- data.frame()
46+
stats <- list()
47+
i <- 1
4648

4749
for (league in leagues) {
48-
url <- glue::glue("{self$BASE_URL}/{league}/{entity}/{type}")
50+
url <- glue::glue("{self$base_url}/{league}/{entity}/{type}")
4951

50-
response <- .execute_query(self, url, query)
52+
response <- .execute_query(self, url, query) %>%
53+
as.data.frame() %>%
54+
dplyr::mutate(competition = league)
5155

52-
stats <- stats %>%
53-
dplyr::bind_rows(response)
56+
stats[[i]] <- response
57+
i <- i + 1
5458
}
5559

60+
stats <- data.table::rbindlist(stats, fill = TRUE)
61+
5662

5763
if (entity %in% c("players", "goalkeepers") & (exists("player_ids_to_filter") && !is.null(player_ids_to_filter))) {
58-
stats <- stats %>% dplyr::filter(rlang::.data$player_id %in% player_ids_to_filter)
64+
stats <- stats %>% dplyr::filter(.data$player_id %in% player_ids_to_filter)
5965
} else if (entity == "teams" & (exists("team_ids_to_filter") && !is.null(team_ids_to_filter))) {
60-
stats <- stats %>% dplyr::filter(rlang::.data$team_id %in% team_ids_to_filter)
66+
stats <- stats %>% dplyr::filter(.data$team_id %in% team_ids_to_filter)
6167
} else if (entity == "games" & (exists("game_ids_to_filter") && !is.null(game_ids_to_filter))) {
62-
stats <- stats %>% dplyr::filter(rlang::.data$game_id %in% game_ids_to_filter)
68+
stats <- stats %>% dplyr::filter(.data$game_id %in% game_ids_to_filter)
6369
}
6470

6571
return(stats)

R-package/R/utils.R

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@
33
return(value)
44
}
55

6-
.check_leagues <- function(leagues, league_options) {
6+
.check_leagues <- function(self, leagues) {
77
if (!missing(leagues)) {
8-
if (any(!leagues %in% league_options)) {
9-
stop(glue::glue("Leagues are limited only to the following options: {paste0(league_options, collapse = ', ')}."))
8+
if (any(!leagues %in% self$LEAGUES)) {
9+
stop(glue::glue("Leagues are limited only to the following options: {paste0(self$LEAGUES, collapse = ', ')}."))
1010
}
1111
}
1212
}
1313

1414
.check_ids_names <- function(ids, names) {
15-
if ((!missing(ids) & !missing(names)) && !is.null(ids) & !is.null(names)) {
15+
if ((!missing(ids) & !missing(names)) && (!is.null(ids) & !is.null(names))) {
1616
stop("Please specify only IDs or names, not both.")
1717
}
1818

@@ -49,14 +49,14 @@
4949
return(names)
5050
}
5151

52-
.single_request <- function(url, query) {
52+
.single_request <- function(self, url, query) {
5353
for (arg_name in names(query)) {
5454
if (length(query[[arg_name]]) > 1) {
5555
query[[arg_name]] <- .collapse_query_string(query[[arg_name]])
5656
}
5757
}
5858

59-
r <- httpcache::GET(url = url, query = query)
59+
r <- httpcache::GET(url = url, query = query, self$httr_configs)
6060
httr::stop_for_status(r)
6161
response <- r %>%
6262
httr::content(as = "text", encoding = "UTF-8") %>%
@@ -66,15 +66,15 @@
6666
}
6767

6868
.execute_query <- function(self, url, query = list()) {
69-
tmp_response <- .single_request(url, query)
69+
tmp_response <- .single_request(self, url, query)
7070
response <- tmp_response
7171

7272
if (is.data.frame(tmp_response)) {
7373
offset <- self$MAX_API_LIMIT
7474

7575
while (nrow(tmp_response) == self$MAX_API_LIMIT) {
7676
query$offset <- offset
77-
tmp_response <- .single_request(url, query)
77+
tmp_response <- .single_request(self, url, query)
7878

7979
response <- response %>% dplyr::bind_rows(tmp_response)
8080
offset <- offset + self$MAX_API_LIMIT

R-package/README.md

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,58 @@
1-
# R package
1+
# itscalledsoccer
2+
3+
Description TK.
4+
5+
## Installation
6+
7+
```r
8+
# Install release version from CRAN
9+
install.packages("itscalledsoccer")
10+
11+
# Install development version from GitHub
12+
devtools::install_github("American-Soccer-Analysis/itscalledsoccer")
13+
```
14+
15+
## Getting Started
16+
17+
Initialize the main class with the `new` method.
18+
19+
```r
20+
asa_client <- AmericanSoccerAnalysis$new()
21+
```
22+
23+
If you're in an environment where a proxy server is required, or if you need to need to alter any other `CURL` options, you can pass any number of `httr` configs when initializing the class. Use these at your own discretion.
24+
25+
```r
26+
asa_client <- AmericanSoccerAnalysis$new(
27+
httr::config(ssl_verifypeer = 0L),
28+
httr::use_proxy("64.251.21.73", 8080)
29+
)
30+
```
31+
32+
## Usage
33+
34+
Any of the `get_*` methods can be used to retrieve the same data made available in the [American Soccer Analysis app](https://app.americansocceranalysis.com/). Partial matches or abbreviations are accepted for any player or team names. For most methods, arguments _must be named_. A few examples are below.
35+
36+
```r
37+
# Get all players named "Dax"
38+
asa_players <- asa_client$get_players(names = "Dax")
39+
40+
# Get season-by-season xG data for all players named "Dax"
41+
asa_xgoals <- asa_client$get_player_xgoals(
42+
leagues = "mls",
43+
player_names = "Dax",
44+
split_by_seasons = TRUE
45+
)
46+
47+
# Get cumulative xPass data for all USL League One teams
48+
asa_xpass <- asa_client$get_team_xpass(
49+
leagues = "usl1"
50+
)
51+
52+
# Get game-by-game goals added (g+) data for all goalkeepers named "Matt Turner"
53+
asa_goals_added <- asa_client$get_goalkeeper_goals_added(
54+
leagues = c("mls", "uslc"),
55+
player_names = "Matt Turner",
56+
split_by_game = TRUE
57+
)
58+
```

R-package/_pkgdown.yml

Whitespace-only changes.

0 commit comments

Comments
 (0)