<p><div><div><div><div data-show-faces="false" data-height="21" data-width="100" data-layout="button_count" data-send="true" data-href="https://www.r-bloggers.com/using-r-to-generate-live-world-cup-notifications/"></div></div><div></div></div><div></div></div>
<div>
(This article was first published on <strong><a href="https://www.openanalytics.eu/blog/2018/07/05/world-cup-notifications-with-r/"> Open Analytics</a></strong>, and kindly contributed to <a rel="nofollow" href="https://www.r-bloggers.com/">R-bloggers)</a> </div>
<p>Here in Belgium, World Cup fever is at fever pitch, but with matches starting<br>
during work hours, how is a desk worker supposed to follow along? By leaving<br>
the R environment? Blasphemy.</p>
<p>Today we show how to use R to generate live desktop notifications for The<br>
Beautiful Game.</p>
<p><img border="0" alt="A notification system preview, free of local bias." width="auto" src="https://i2.wp.com/www.openanalytics.eu/blog-img/worldcup-notification.png?w=456&ssl=1" data-recalc-dims="1">
</p>
<p>
A notification system preview, free of local bias.
</p>
<h3>Overview</h3>
<p>We break the process of producing a live score notification into the following<br>
steps:</p>
<ol>
<li>Get the score</li>
<li>Check if the score has changed</li>
<li>If yes, show a notification</li>
<li>Repeat steps 1-3 every minute</li>
</ol>
<h3>Step 1: Getting the Score</h3>
<p>FIFA <a rel="nofollow" href="https://api.fifa.com/api/v1/live/football/" target="_blank">provides an API</a><br>
with detailed information about matches. The API provides a list of each day’s<br>
matches in JSON. A full description of the fields is provided in the <a rel="nofollow" href="https://api.qa.fifa.com/Help/ResourceModel?modelName=LiveMatch" target="_blank">API<br>
documentation</a>.</p>
<p>For the purposes of this exercise, we need the scores<br>
(<code>AggregateHomeTeamScore</code>, <code>AggregateAwayTeamScore</code>, <code>HomeTeamPenaltyScore</code>,<br>
<code>AwayTeamPenaltyScore</code>), and team names (<code>HomeTeam.TeamName</code>,<br>
<code>AwayTeam.TeamName</code>).<br>
Additionally, we subset the data to the active World Cup matches by filtering to<br>
matches with <code>IdSeason</code> of 254645 (the World Cup competition<br>
ID) and <code>MatchStatus</code> of 3 (the live match status ID).</p>
<p>As functions, this looks like:</p>
<pre><code>readLiveMatchScore &lt;- function() {
  # reading in the API data
  worldcupDataDF &lt;- 
      jsonlite::fromJSON(&quot;https://api.fifa.com/api/v1/live/football/&quot;)$Results
  # which World Cup match is currently active?
  worldcupMatchIdx &lt;- which(worldcupDataDF$IdSeason == 254645 &amp; 
          worldcupDataDF$MatchStatus == 3)
  
  if (length(worldcupMatchIdx) != 1) { # no matches or more than 1 match
    liveScore &lt;- NULL
  } else {
    # get the score
    liveScore &lt;- unlist(worldcupDataDF[worldcupMatchIdx, 
            c(&quot;AggregateHomeTeamScore&quot;, &quot;AggregateAwayTeamScore&quot;, 
              &quot;HomeTeamPenaltyScore&quot;, &quot;AwayTeamPenaltyScore&quot;)])
    
    homeTeam &lt;- worldcupDataDF$HomeTeam$TeamName[[worldcupMatchIdx]]$Description
    awayTeam &lt;- worldcupDataDF$AwayTeam$TeamName[[worldcupMatchIdx]]$Description
    names(liveScore) &lt;- rep(c(homeTeam, awayTeam), 2)
  }
  liveScore
}

scoreAsString &lt;- function(matchScore, penalties = FALSE) {
  out &lt;- paste(names(matchScore)[1], &quot; - &quot;, names(matchScore)[2], &quot;:&quot;, 
      matchScore[1], &quot; - &quot;, matchScore[2])
  if (penalties &amp;&amp; matchScore[1] == matchScore[2])
    out &lt;- paste0(out, &quot; (pen. &quot;, matchScore[3], &quot; - &quot;, matchScore[4], &quot;)&quot; )
  out
}
</code></pre>
<h3>Step 2: Check If the Score Has Changes</h3>
<p>To check if the score has changed, we store the previous score and<br>
check if it differs from the current score. If there is a change, we send a<br>
notification.</p>
<pre><code>checkScoreAndNotify &lt;- function(prevScore = NULL) {
  newScore &lt;- readLiveMatchScore()
  if (is.null(newScore) &amp;&amp; is.null(prevScore)) {
    # nothing to do here
  } else if (is.null(newScore) &amp;&amp; !is.null(prevScore)) {
    # end of the game
    sendNotification(title = &quot;Match ended&quot;, message = scoreAsString(prevScore, TRUE))
  
  } else if (is.null(prevScore) &amp;&amp; !is.null(newScore)) {
    # start of the game
    sendNotification(title = &quot;Match started&quot;, message = scoreAsString(newScore))
  
  } else if (!is.null(prevScore) &amp;&amp; !is.null(newScore) &amp;&amp; !identical(newScore, prevScore)) {
    # change in the score
    sendNotification(title = &quot;GOAL!&quot;, message = scoreAsString(newScore))
  }
  return(newScore)
}
</code></pre>
<h3>Step 3: Display Notification</h3>
<p>To create a notification, we use the <tt>notifier</tt> R<br>
package (now archived on CRAN).<br>
It can be installed via <tt>devtools</tt>:</p>
<pre><code>devtools::install_version(&quot;notifier&quot;)
</code></pre>
<p>or via the CRAN Archive by giving the URL:</p>
<pre><code>url &lt;- &quot;https://cran.rstudio.com/src/contrib/Archive/notifier/notifier_1.0.0.tar.gz&quot;
install.packages(url, type = &quot;source&quot;, repos = NULL)
</code></pre>
<p>To spice up the notification, we add the World Cup logo in the<br>
notification area.</p>
<pre><code># get the logo from FIFA website
download.file(&quot;https://api.fifa.com/api/v1/picture/tournaments-sq-4/254645_w&quot;, 
    &quot;logo.png&quot;)

sendNotification &lt;- function(title = &quot;&quot;, message) {
  notifier::notify(title = title, msg = message, image = normalizePath(&quot;logo.png&quot;))
}
</code></pre>
<h3>Step 4: Repeat Every Minute</h3>
<p>We use the <a rel="nofollow" href="https://cran.r-project.org/package=later" target="_blank"><tt>later</tt></a> package to<br>
query the scores API repeatedly without blocking the R session. Taking<br>
inspiration from Yihui Xie’s blog<br>
<a rel="nofollow" href="https://yihui.name/en/2017/10/later-recursion/" target="_blank">Schedule R code to Be Executed Periodically in the<br>
Current R Session</a>,<br>
we write a recursive function to query the scores. The previous score is<br>
tracked using a global variable.</p>
<pre><code>getScoreUpdates &lt;- function() {
  prevScore &lt;- checkScoreAndNotify(prevScore)
  later::later(getScoreUpdates, delay = 60)
}
</code></pre>
<h3>All Together Now</h3>
<p>To run this entire process, we simply initialize the<br>
global <code>prevScore</code> variable and launch the recursive function<br>
<code>getScoreUpdates</code>:</p>
<pre><code>prevScore &lt;- NULL 
getScoreUpdates()
</code></pre>
<h3>Complete script</h3>
<pre><code>## 0. preparatory steps
if (!require(&quot;notifier&quot;, character.only = TRUE)) {
  url &lt;- &quot;https://cloud.r-project.org/src/contrib/Archive/notifier/notifier_1.0.0.tar.gz&quot;
  install.packages(url, type = &quot;source&quot;, repos = NULL)
}
if (!require(&quot;later&quot;, character.only = TRUE)) {
  install.packages(&quot;later&quot;)
}
download.file(&quot;https://api.fifa.com/api/v1/picture/tournaments-sq-4/254645_w&quot;, &quot;logo.png&quot;)

## 1. get match score
readLiveMatchScore &lt;- function() {
  # reading in the API data
  worldcupDataDF &lt;- 
      jsonlite::fromJSON(&quot;https://api.fifa.com/api/v1/live/football/&quot;)$Results
  # which World Cup match is currently active?
  worldcupMatchIdx &lt;- which(worldcupDataDF$IdSeason == 254645 &amp; 
          worldcupDataDF$MatchStatus == 3)
  
  if (length(worldcupMatchIdx) != 1) { # no matches or more than 1 match
    liveScore &lt;- NULL
  } else {
    # get the score
    liveScore &lt;- unlist(worldcupDataDF[worldcupMatchIdx, 
            c(&quot;AggregateHomeTeamScore&quot;, &quot;AggregateAwayTeamScore&quot;, 
                &quot;HomeTeamPenaltyScore&quot;, &quot;AwayTeamPenaltyScore&quot;)])
    
    homeTeam &lt;- worldcupDataDF$HomeTeam$TeamName[[worldcupMatchIdx]]$Description
    awayTeam &lt;- worldcupDataDF$AwayTeam$TeamName[[worldcupMatchIdx]]$Description
    names(liveScore) &lt;- rep(c(homeTeam, awayTeam), 2)
  }
  liveScore
}

scoreAsString &lt;- function(matchScore, penalties = FALSE) {
  out &lt;- paste(names(matchScore)[1], &quot; - &quot;, names(matchScore)[2], &quot;:&quot;, 
      matchScore[1], &quot; - &quot;, matchScore[2])
  if (penalties &amp;&amp; matchScore[1] == matchScore[2])
    out &lt;- paste0(out, &quot; (pen. &quot;, matchScore[3], &quot; - &quot;, matchScore[4], &quot;)&quot; )
  out
}

## 2. check for score changes
checkScoreAndNotify &lt;- function(prevScore = NULL) {
  newScore &lt;- readLiveMatchScore()
  if (is.null(newScore) &amp;&amp; is.null(prevScore)) {
    # nothing to do here
  } else if (is.null(newScore) &amp;&amp; !is.null(prevScore)) {
    # end of the game
    sendNotification(title = &quot;Match ended&quot;, message = scoreAsString(prevScore, TRUE))
    
  } else if (is.null(prevScore) &amp;&amp; !is.null(newScore)) {
    # start of the game
    sendNotification(title = &quot;Match started&quot;, message = scoreAsString(newScore))
    
  } else if (!is.null(prevScore) &amp;&amp; !is.null(newScore) &amp;&amp; !identical(newScore, prevScore)) {
    # change in the score
    sendNotification(title = &quot;GOAL!&quot;, message = scoreAsString(newScore))
  }
  return(newScore)
}

## 3. send notification
sendNotification &lt;- function(title = &quot;&quot;, message) {
  notifier::notify(title = title, msg = message, image = normalizePath(&quot;logo.png&quot;))
}

## 4. check score every minute
getScoreUpdates &lt;- function() {
  prevScore &lt;- checkScoreAndNotify(prevScore)
  later::later(getScoreUpdates, delay = 60)
}

## 5. launch everything
prevScore &lt;- NULL 
getScoreUpdates()
</code></pre>
<h3>Wrap-Up</h3>
<p><img border="0" alt="World Cup Notification" width="auto" src="https://i2.wp.com/www.openanalytics.eu/blog-img/worldcup-notification.png?w=456&ssl=1" data-recalc-dims="1"></p>
<p>That’s our quick take on generating live score notifications using R. By using<br>
a different API or alternative competition codes, this approach can be<br>
generalized to generate notifications for other settings.</p>
<p>Oh, and if you’re looking to predict the winner of the World Cup using<br>
statistics and historical trends, the <a rel="nofollow" href="https://www.bbc.com/sport/football/44431262" target="_blank">BBC has you<br>
covered</a>. May the loudest vuvuzela<br>
win!</p>
	<p>
<div>
<div>To <strong>leave a comment</strong> for the author, please follow the link and comment on their blog: <strong><a href="https://www.openanalytics.eu/blog/2018/07/05/world-cup-notifications-with-r/"> Open Analytics</a></strong>.</div>
<hr>
<a rel="nofollow" href="https://www.r-bloggers.com/">R-bloggers.com</a> offers <strong><a rel="nofollow" href="https://feedburner.google.com/fb/a/mailverify?uri=RBloggers">daily e-mail updates</a></strong> about <a rel="nofollow" href="https://www.r-project.org/" title="The R Project for Statistical Computing">R</a> news and <a rel="nofollow" href="https://www.r-bloggers.com/search/tutorial" title="R tutorials">tutorials</a> on topics such as: <a rel="nofollow" href="https://www.r-bloggers.com/search/data%20science" title="Data science">Data science</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/Big%20Data" title="Big Data">Big Data, <a rel="nofollow" href="https://www.r-users.com/" title="R jobs">R jobs</a>, visualization (<a rel="nofollow" href="https://www.r-bloggers.com/search/ggplot2" title="ggplot and ggplot2 tutorials">ggplot2</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/boxplot" title="Boxplots using lattice and ggplot2 tutorials">Boxplots</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/map" title="Maps and gis">maps</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/animation" title="Animation in R">animation</a>), programming (<a rel="nofollow" href="https://www.r-bloggers.com/search/RStudio" title="RStudio IDE for R">RStudio</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/sweave" title="Sweave and literate programming">Sweave</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/LaTeX" title="LaTeX in R">LaTeX</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/SQL" title="SQL and databases">SQL</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/eclipse" title="Eclipse IDE for R">Eclipse</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/git" title="git and github, Version Control System">git</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/hadoop" title="Large data in R using Hadoop">hadoop</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/Web+Scraping" title="Web Scraping of google, facebook, yahoo, twitter and more using R">Web Scraping</a>) statistics (<a rel="nofollow" href="https://www.r-bloggers.com/search/regression" title="Regressions and ANOVA analysis tutorials">regression</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/PCA" title="principal component analysis tutorial">PCA</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/time+series" title="Time series">time series</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/trading" title="finance trading">trading</a>) and more...
<img width="1" alt="" src="http://feeds.feedburner.com/~r/RBloggers/~4/RWdZYZySozs" height="1"></a></div>