---
title: The FootballData package and dashboard
description: Getting football data, then summarising a fan's mood based on the
date: 2021-01-28
date-modified: 2021-01-28
image: featured.png
author: James Black
tags: []
categories: [R, Data Analysis, Package, Football]
---
## Table of Contents
::: {.toc}
:::
## Intro
A colleague made the following site in jest (https://coppingr.com/), as an
outgoing present for the founding head of our department. Inspired this effort,
I wondered whether you could make a service to predict a football supporters mood, by
leveraging one of the many APIs out there that serve live or near live football
data.
This post is an example of using the package I wrote that spawned from that idea.
Documentation: https://epijim.github.io/FootballData/
## Setup environment
``` r
library(FootballData)
library(tidyverse)
library(glue)
library(emo) #devtools::install_github("hadley/emo")
# table helper - switch between html vs markdown
table_it <- function(x, type = "markdown"){
if(type == "markdown") return(x %>% knitr::kable(escape = FALSE))
x %>% kableExtra::kbl(escape = F) %>%
kableExtra::kable_classic("striped", full_width = TRUE)
}
```
## Get competitions
First I will pull all the leagues that are in the free tier, from this
particular API (football-data.org) and show
them on a table.
```R
# Get competitions
competitions <- football_get_competition() %>%
filter(
# filter to what's in the free plan
plan == "TIER_ONE"
&
# The data structures returned for country level competitions is
# different, so let's remove it
!country %in% c("World","Europe"))
competitions %>% select(flag,country,league) %>% table_it()
```
| flag | country | league |
| :------------------------------------------------------------------------------------------------------------ | :---------- | :--------------- |
| NA | Brazil | Série A |
| <img src='https://upload.wikimedia.org/wikipedia/en/a/ae/Flag_of_the_United_Kingdom.svg' width='24' align='left'></img> | England | Championship |
| <img src='https://upload.wikimedia.org/wikipedia/en/a/ae/Flag_of_the_United_Kingdom.svg' width='24' align='left'></img> | England | Premier League |
| <img src='https://upload.wikimedia.org/wikipedia/en/c/c3/Flag_of_France.svg' width='24' align='left'></img> | France | Ligue 1 |
| <img src='https://upload.wikimedia.org/wikipedia/commons/b/ba/Flag_of_Germany.svg' width='24' align='left'></img> | Germany | Bundesliga |
| <img src='https://upload.wikimedia.org/wikipedia/en/0/03/Flag_of_Italy.svg' width='24' align='left'></img> | Italy | Serie A |
| <img src='https://upload.wikimedia.org/wikipedia/commons/2/20/Flag_of_the_Netherlands.svg' width='24' align='left'></img> | Netherlands | Eredivisie |
| <img src='https://upload.wikimedia.org/wikipedia/commons/5/5c/Flag_of_Portugal.svg' width='24' align='left'></img> | Portugal | Primeira Liga |
| <img src='https://upload.wikimedia.org/wikipedia/en/9/9a/Flag_of_Spain.svg' width='24' align='left'></img> | Spain | Primera Division |
## Dive into a league
Now I'll pick my local premier league (`Bundesliga`) and pull info on it's current
standings.
``` r
# Get league info
league <- competitions %>%
filter(country == "Germany" & league == "Bundesliga") %>%
pull(competition_id) %>%
football_get_standing()
league %>%
mutate(team = paste(crest,team)) %>%
select(team,playedGames,form,points) %>%
table_it()
```
| team | playedGames | form | points |
| :--------------------------------------------------------------------------------------------- | ----------: | :-------- | -----: |
| <img src='https://crests.football-data.org/5.svg' width='24' align='left'></img> FC Bayern München | 20 | W,W,W,W,W | 48 |
| <img src='https://crests.football-data.org/721.svg' width='24' align='left'></img> RB Leipzig | 20 | W,W,L,W,D | 41 |
| <img src='https://crests.football-data.org/11.svg' width='24' align='left'></img> VfL Wolfsburg | 20 | W,W,W,W,D | 38 |
| <img src='https://crests.football-data.org/19.svg' width='24' align='left'></img> Eintracht Frankfurt | 20 | W,W,W,D,W | 36 |
| <img src='https://crests.football-data.org/3.svg' width='24' align='left'></img> Bayer 04 Leverkusen | 20 | W,L,L,W,L | 35 |
| <img src='https://crests.football-data.org/4.svg' width='24' align='left'></img> Borussia Dortmund | 20 | L,W,L,L,D | 32 |
| <img src='https://crests.football-data.org/18.svg' width='24' align='left'></img> Borussia Mönchengladbach | 20 | L,D,W,W,D | 32 |
| <img src='https://crests.football-data.org/17.svg' width='24' align='left'></img> SC Freiburg | 20 | W,L,W,D,L | 30 |
| <img src='https://crests.football-data.org/28.svg' width='24' align='left'></img> 1. FC Union Berlin | 20 | L,D,L,L,W | 29 |
| <img src='https://crests.football-data.org/10.svg' width='24' align='left'></img> VfB Stuttgart | 20 | L,W,L,L,D | 25 |
| <img src='https://crests.football-data.org/12.svg' width='24' align='left'></img> SV Werder Bremen | 19 | D,W,L,W,D | 22 |
| <img src='https://crests.football-data.org/2.svg' width='24' align='left'></img> TSG 1899 Hoffenheim | 20 | L,L,W,W,D | 22 |
| <img src='https://crests.football-data.org/16.svg' width='24' align='left'></img> FC Augsburg | 20 | L,L,W,L,L | 22 |
| <img src='https://crests.football-data.org/1.svg' width='24' align='left'></img> 1. FC Köln | 20 | W,W,L,W,D | 21 |
| <img src='https://crests.football-data.org/9.svg' width='24' align='left'></img> Hertha BSC | 20 | L,L,L,L,D | 17 |
| <img src='https://crests.football-data.org/38.svg' width='24' align='left'></img> DSC Arminia Bielefeld | 19 | L,L,W,D,W | 17 |
| <img src='https://crests.football-data.org/15.svg' width='24' align='left'></img> 1. FSV Mainz 05 | 20 | W,L,W,L,D | 13 |
| <img src='https://crests.football-data.org/6.svg' width='24' align='left'></img> FC Schalke 04 | 20 | L,D,L,L,L | 8 |
## Get upcoming games for a team
From the API, I can also get more details on my local team.
``` r
team_of_interest <- "SC Freiburg"
league %>%
filter(team == team_of_interest) %>%
pull(team_id) %>%
football_get_upcoming() %>%
arrange(utcDate) %>%
slice(1) %>%
mutate(
text = glue(
"Today is {Sys.Date()}\n",
"{team_of_interest}'s next game is on {as.Date(utcDate)}\n",
"Home: {homeTeam$name}; Away {awayTeam$name}"
)
) %>%
pull(text)
#> Today is 2021-02-09
#> SC Freiburg's next game is on 2021-02-13
#> Home: SV Werder Bremen; Away SC Freiburg
```
## Calculate the moods
Now that I have used the data prep functions, I can jump into the 'fun' bit
of this package. Using the _live data_ I can run simple rules to try and
figure out what the mood of a supporter may be right now.
Below - I run the mood logic on the Bundesliga.
``` r
emoji_bad <- emo::ji("poop")
emoji_good <- emo::ji("beer")
add_bad <- function(x){
gsub("-",emoji_bad,x = x)
}
add_good <- function(x){
gsub("\\+",emoji_good,x = x)
}
league %>%
football_calculate_competition_metrics() %>%
mutate(
team = paste(crest,team),
metric_mood = as.character(metric_mood),
metric_mood = case_when(
str_detect(metric_mood,"Jubilant") ~ kableExtra::cell_spec(
metric_mood, color = "black", background = "#98FB98"
),
str_detect(metric_mood,"Optimistic") ~ kableExtra::cell_spec(
metric_mood, color = "black", background = "#e5f5f9"
),
str_detect(metric_mood,"Ambivalent") ~ kableExtra::cell_spec(
metric_mood, color = "black", background = "#f7fcb9"
),
str_detect(metric_mood,"Doldrums") ~ kableExtra::cell_spec(
metric_mood, color = "black", background = "#ffc4c4"
),
TRUE ~ metric_mood
)
) %>%
rename(mood = metric_mood,numeric_mood = metric_mood_numeric) %>%
mutate_at(vars(matches("metric_")),add_bad) %>%
mutate_all(add_good) %>%
select(team,points,starts_with("metric_"),numeric_mood,mood) %>%
table_it()
```
{{% callout note %}}
If viewing this on a standard screen - you man need to scroll right to see
all the columns.
{{% /callout %}}
| team | points | metric\_season\_position | metric\_recent\_wins | metric\_back\_of\_net | numeric\_mood | mood |
| :--------------------------------------------------------------------------------------------- | :----- | :------------------------- | :------------------------------ | :-------------------------- | :------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <img src='https://crests.football-data.org/5.svg' width='24' align='left'></img> FC Bayern München | 48 | 🍺🍺🍺🍺 top of the league | 🍺🍺🍺🍺 5 win streak | 🍺🍺🍺 strikers are on form | 11 | <span style=" color: black !important;border-radius: 4px; padding-right: 4px; padding-left: 4px; background-color: #98FB98 !important;">Jubilant: FC Bayern München are on a roll</span> |
| <img src='https://crests.football-data.org/721.svg' width='24' align='left'></img> RB Leipzig | 41 | 🍺🍺 strong league position | 🍺🍺 recent wins | 🍺🍺🍺 strikers are on form | 7 | <span style=" color: black !important;border-radius: 4px; padding-right: 4px; padding-left: 4px; background-color: #98FB98 !important;">Jubilant: RB Leipzig are on a roll</span> |
| <img src='https://crests.football-data.org/11.svg' width='24' align='left'></img> VfL Wolfsburg | 38 | 🍺🍺 strong league position | 🍺🍺 recent wins | 🍺 scored more than conceded | 5 | <span style=" color: black !important;border-radius: 4px; padding-right: 4px; padding-left: 4px; background-color: #e5f5f9 !important;">Optimistic: VfL Wolfsburg are doing well</span> |
| <img src='https://crests.football-data.org/19.svg' width='24' align='left'></img> Eintracht Frankfurt | 36 | 🍺 safe in middle of league | 🍺🍺 recent wins | 🍺 scored more than conceded | 4 | <span style=" color: black !important;border-radius: 4px; padding-right: 4px; padding-left: 4px; background-color: #e5f5f9 !important;">Optimistic: Eintracht Frankfurt are doing well</span> |
| <img src='https://crests.football-data.org/3.svg' width='24' align='left'></img> Bayer 04 Leverkusen | 35 | 🍺 safe in middle of league | 💩💩 more recent losses than wins | 🍺 scored more than conceded | 0 | <span style=" color: black !important;border-radius: 4px; padding-right: 4px; padding-left: 4px; background-color: #f7fcb9 !important;">Ambivalent: Bayer 04 Leverkusen aren’t doing well, but hey - at least they aren’t FC Schalke 04</span> |
| <img src='https://crests.football-data.org/4.svg' width='24' align='left'></img> Borussia Dortmund | 32 | 🍺 safe in middle of league | 💩💩 more recent losses than wins | 🍺 scored more than conceded | 0 | <span style=" color: black !important;border-radius: 4px; padding-right: 4px; padding-left: 4px; background-color: #f7fcb9 !important;">Ambivalent: Borussia Dortmund aren’t doing well, but hey - at least they aren’t FC Schalke 04</span> |
| <img src='https://crests.football-data.org/18.svg' width='24' align='left'></img> Borussia Mönchengladbach | 32 | 🍺 safe in middle of league | drew last game | 🍺 scored more than conceded | 2 | <span style=" color: black !important;border-radius: 4px; padding-right: 4px; padding-left: 4px; background-color: #e5f5f9 !important;">Optimistic: Borussia Mönchengladbach are doing well</span> |
| <img src='https://crests.football-data.org/17.svg' width='24' align='left'></img> SC Freiburg | 30 | 🍺 safe in middle of league | 💩 lost last game | 🍺 scored more than conceded | 1 | <span style=" color: black !important;border-radius: 4px; padding-right: 4px; padding-left: 4px; background-color: #e5f5f9 !important;">Optimistic: SC Freiburg are doing well</span> |
| <img src='https://crests.football-data.org/28.svg' width='24' align='left'></img> 1. FC Union Berlin | 29 | 💩 so so season | 🍺 won last game | 🍺 scored more than conceded | 1 | <span style=" color: black !important;border-radius: 4px; padding-right: 4px; padding-left: 4px; background-color: #e5f5f9 !important;">Optimistic: 1. FC Union Berlin are doing well</span> |
| <img src='https://crests.football-data.org/10.svg' width='24' align='left'></img> VfB Stuttgart | 25 | 💩 so so season | 💩💩 more recent losses than wins | 🍺 scored more than conceded | \-2 | <span style=" color: black !important;border-radius: 4px; padding-right: 4px; padding-left: 4px; background-color: #f7fcb9 !important;">Ambivalent: VfB Stuttgart aren’t doing well, but hey - at least they aren’t FC Schalke 04</span> |
| <img src='https://crests.football-data.org/12.svg' width='24' align='left'></img> SV Werder Bremen | 22 | 💩 so so season | drew last game | 💩 conceded more than scored | \-2 | <span style=" color: black !important;border-radius: 4px; padding-right: 4px; padding-left: 4px; background-color: #f7fcb9 !important;">Ambivalent: SV Werder Bremen aren’t doing well, but hey - at least they aren’t FC Schalke 04</span> |
| <img src='https://crests.football-data.org/2.svg' width='24' align='left'></img> TSG 1899 Hoffenheim | 22 | 💩 so so season | drew last game | 💩 conceded more than scored | \-2 | <span style=" color: black !important;border-radius: 4px; padding-right: 4px; padding-left: 4px; background-color: #f7fcb9 !important;">Ambivalent: TSG 1899 Hoffenheim aren’t doing well, but hey - at least they aren’t FC Schalke 04</span> |
| <img src='https://crests.football-data.org/16.svg' width='24' align='left'></img> FC Augsburg | 22 | 💩 so so season | 💩💩 more recent losses than wins | 💩 conceded more than scored | \-4 | <span style=" color: black !important;border-radius: 4px; padding-right: 4px; padding-left: 4px; background-color: #f7fcb9 !important;">Ambivalent: FC Augsburg aren’t doing well, but hey - at least they aren’t FC Schalke 04</span> |
| <img src='https://crests.football-data.org/1.svg' width='24' align='left'></img> 1. FC Köln | 21 | 💩 so so season | 🍺🍺 recent wins | 💩 conceded more than scored | 0 | <span style=" color: black !important;border-radius: 4px; padding-right: 4px; padding-left: 4px; background-color: #f7fcb9 !important;">Ambivalent: 1. FC Köln aren’t doing well, but hey - at least they aren’t FC Schalke 04</span> |
| <img src='https://crests.football-data.org/9.svg' width='24' align='left'></img> Hertha BSC | 17 | 💩💩 poor season | 💩💩 no recent wins | 💩 conceded more than scored | \-5 | <span style=" color: black !important;border-radius: 4px; padding-right: 4px; padding-left: 4px; background-color: #ffc4c4 !important;">Doldrums: Hertha BSC are doing poor. Now’s a good time to bad mouth the manager.</span> |
| <img src='https://crests.football-data.org/38.svg' width='24' align='left'></img> DSC Arminia Bielefeld | 17 | 💩💩 poor season | 🍺 won last game | 💩💩💩 defense is a seive | \-4 | <span style=" color: black !important;border-radius: 4px; padding-right: 4px; padding-left: 4px; background-color: #f7fcb9 !important;">Ambivalent: DSC Arminia Bielefeld aren’t doing well, but hey - at least they aren’t FC Schalke 04</span> |
| <img src='https://crests.football-data.org/15.svg' width='24' align='left'></img> 1. FSV Mainz 05 | 13 | 💩💩💩💩 relagation? | drew last game | 💩💩💩 defense is a seive | \-7 | <span style=" color: black !important;border-radius: 4px; padding-right: 4px; padding-left: 4px; background-color: #ffc4c4 !important;">Doldrums: 1. FSV Mainz 05 are doing poor. Now’s a good time to bad mouth the manager.</span> |
| <img src='https://crests.football-data.org/6.svg' width='24' align='left'></img> FC Schalke 04 | 8 | 💩💩💩💩 relagation? | 💩💩 no recent wins | 💩💩💩 defense is a seive | \-9 | <span style=" color: black !important;border-radius: 4px; padding-right: 4px; padding-left: 4px; background-color: #ffc4c4 !important;">Doldrums: FC Schalke 04 are doing poorly. Avoid supporters at all costs.</span> |
## Shiny app
Not interested in the Bundesliga? I also wrapped the package in a shiny app
available here: https://epijim.shinyapps.io/football_moods/
## Exactly what are the metrics that define mood?
The current logic is contained in the following [Github file](https://github.com/epijim/FootballData/blob/main/R/football_calculate_metrics.R).