Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
Here in Belgium, World Cup fever is at fever pitch, but with matches starting during work hours, how is a desk worker supposed to follow along? By leaving the R environment? Blasphemy.
Today we show how to use R to generate live desktop notifications for The Beautiful Game.
A notification system preview, free of local bias.
Overview
We break the process of producing a live score notification into the following steps:
- Get the score
- Check if the score has changed
- If yes, show a notification
- Repeat steps 1-3 every minute
Step 1: Getting the Score
FIFA provides an API with detailed information about matches. The API provides a list of each day’s matches in JSON. A full description of the fields is provided in the API documentation.
For the purposes of this exercise, we need the scores
(AggregateHomeTeamScore
, AggregateAwayTeamScore
, HomeTeamPenaltyScore
,
AwayTeamPenaltyScore
), and team names (HomeTeam.TeamName
,
AwayTeam.TeamName
).
Additionally, we subset the data to the active World Cup matches by filtering to
matches with IdSeason
of 254645 (the World Cup competition
ID) and MatchStatus
of 3 (the live match status ID).
As functions, this looks like:
readLiveMatchScore <- function() { # reading in the API data worldcupDataDF <- jsonlite::fromJSON("https://api.fifa.com/api/v1/live/football/")$Results # which World Cup match is currently active? worldcupMatchIdx <- which(worldcupDataDF$IdSeason == 254645 & worldcupDataDF$MatchStatus == 3) if (length(worldcupMatchIdx) != 1) { # no matches or more than 1 match liveScore <- NULL } else { # get the score liveScore <- unlist(worldcupDataDF[worldcupMatchIdx, c("AggregateHomeTeamScore", "AggregateAwayTeamScore", "HomeTeamPenaltyScore", "AwayTeamPenaltyScore")]) homeTeam <- worldcupDataDF$HomeTeam$TeamName[[worldcupMatchIdx]]$Description awayTeam <- worldcupDataDF$AwayTeam$TeamName[[worldcupMatchIdx]]$Description names(liveScore) <- rep(c(homeTeam, awayTeam), 2) } liveScore } scoreAsString <- function(matchScore, penalties = FALSE) { out <- paste(names(matchScore)[1], " - ", names(matchScore)[2], ":", matchScore[1], " - ", matchScore[2]) if (penalties && matchScore[1] == matchScore[2]) out <- paste0(out, " (pen. ", matchScore[3], " - ", matchScore[4], ")" ) out }
Step 2: Check If the Score Has Changes
To check if the score has changed, we store the previous score and check if it differs from the current score. If there is a change, we send a notification.
checkScoreAndNotify <- function(prevScore = NULL) { newScore <- readLiveMatchScore() if (is.null(newScore) && is.null(prevScore)) { # nothing to do here } else if (is.null(newScore) && !is.null(prevScore)) { # end of the game sendNotification(title = "Match ended", message = scoreAsString(prevScore, TRUE)) } else if (is.null(prevScore) && !is.null(newScore)) { # start of the game sendNotification(title = "Match started", message = scoreAsString(newScore)) } else if (!is.null(prevScore) && !is.null(newScore) && !identical(newScore, prevScore)) { # change in the score sendNotification(title = "GOAL!", message = scoreAsString(newScore)) } return(newScore) }
Step 3: Display Notification
To create a notification, we use the notifier R package (now archived on CRAN). It can be installed via devtools:
devtools::install_version("notifier")
or via the CRAN Archive by giving the URL:
url <- "https://cran.rstudio.com/src/contrib/Archive/notifier/notifier_1.0.0.tar.gz" install.packages(url, type = "source", repos = NULL)
To spice up the notification, we add the World Cup logo in the notification area.
# get the logo from FIFA website download.file("https://api.fifa.com/api/v1/picture/tournaments-sq-4/254645_w", "logo.png") sendNotification <- function(title = "", message) { notifier::notify(title = title, msg = message, image = normalizePath("logo.png")) }
Step 4: Repeat Every Minute
We use the later package to query the scores API repeatedly without blocking the R session. Taking inspiration from Yihui Xie’s blog Schedule R code to Be Executed Periodically in the Current R Session, we write a recursive function to query the scores. The previous score is tracked using a global variable.
getScoreUpdates <- function() { prevScore <<- checkScoreAndNotify(prevScore) later::later(getScoreUpdates, delay = 60) }
All Together Now
To run this entire process, we simply initialize the
global prevScore
variable and launch the recursive function
getScoreUpdates
:
prevScore <- NULL getScoreUpdates()
Complete script
## 0. preparatory steps if (!require("notifier", character.only = TRUE)) { url <- "https://cloud.r-project.org/src/contrib/Archive/notifier/notifier_1.0.0.tar.gz" install.packages(url, type = "source", repos = NULL) } if (!require("later", character.only = TRUE)) { install.packages("later") } download.file("https://api.fifa.com/api/v1/picture/tournaments-sq-4/254645_w", "logo.png") ## 1. get match score readLiveMatchScore <- function() { # reading in the API data worldcupDataDF <- jsonlite::fromJSON("https://api.fifa.com/api/v1/live/football/")$Results # which World Cup match is currently active? worldcupMatchIdx <- which(worldcupDataDF$IdSeason == 254645 & worldcupDataDF$MatchStatus == 3) if (length(worldcupMatchIdx) != 1) { # no matches or more than 1 match liveScore <- NULL } else { # get the score liveScore <- unlist(worldcupDataDF[worldcupMatchIdx, c("AggregateHomeTeamScore", "AggregateAwayTeamScore", "HomeTeamPenaltyScore", "AwayTeamPenaltyScore")]) homeTeam <- worldcupDataDF$HomeTeam$TeamName[[worldcupMatchIdx]]$Description awayTeam <- worldcupDataDF$AwayTeam$TeamName[[worldcupMatchIdx]]$Description names(liveScore) <- rep(c(homeTeam, awayTeam), 2) } liveScore } scoreAsString <- function(matchScore, penalties = FALSE) { out <- paste(names(matchScore)[1], " - ", names(matchScore)[2], ":", matchScore[1], " - ", matchScore[2]) if (penalties && matchScore[1] == matchScore[2]) out <- paste0(out, " (pen. ", matchScore[3], " - ", matchScore[4], ")" ) out } ## 2. check for score changes checkScoreAndNotify <- function(prevScore = NULL) { newScore <- readLiveMatchScore() if (is.null(newScore) && is.null(prevScore)) { # nothing to do here } else if (is.null(newScore) && !is.null(prevScore)) { # end of the game sendNotification(title = "Match ended", message = scoreAsString(prevScore, TRUE)) } else if (is.null(prevScore) && !is.null(newScore)) { # start of the game sendNotification(title = "Match started", message = scoreAsString(newScore)) } else if (!is.null(prevScore) && !is.null(newScore) && !identical(newScore, prevScore)) { # change in the score sendNotification(title = "GOAL!", message = scoreAsString(newScore)) } return(newScore) } ## 3. send notification sendNotification <- function(title = "", message) { notifier::notify(title = title, msg = message, image = normalizePath("logo.png")) } ## 4. check score every minute getScoreUpdates <- function() { prevScore <<- checkScoreAndNotify(prevScore) later::later(getScoreUpdates, delay = 60) } ## 5. launch everything prevScore <- NULL getScoreUpdates()
Wrap-Up
That’s our quick take on generating live score notifications using R. By using a different API or alternative competition codes, this approach can be generalized to generate notifications for other settings.
Oh, and if you’re looking to predict the winner of the World Cup using statistics and historical trends, the BBC has you covered. May the loudest vuvuzela win!
R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.