Getting football data, then summarising a fan’s mood based on the
Author
James Black
Published
January 28, 2021
Modified
January 28, 2021
Table of Contents
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.
library(FootballData)library(tidyverse)library(glue)library(emo) #devtools::install_github("hadley/emo")# table helper - switch between html vs markdowntable_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.
# Get competitionscompetitions <-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
England
Championship
England
Premier League
France
Ligue 1
Germany
Bundesliga
Italy
Serie A
Netherlands
Eredivisie
Portugal
Primeira Liga
Spain
Primera Division
Dive into a league
Now I’ll pick my local premier league (Bundesliga) and pull info on it’s current standings.
# Get league infoleague <- 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
FC Bayern München
20
W,W,W,W,W
48
RB Leipzig
20
W,W,L,W,D
41
VfL Wolfsburg
20
W,W,W,W,D
38
Eintracht Frankfurt
20
W,W,W,D,W
36
Bayer 04 Leverkusen
20
W,L,L,W,L
35
Borussia Dortmund
20
L,W,L,L,D
32
Borussia Mönchengladbach
20
L,D,W,W,D
32
SC Freiburg
20
W,L,W,D,L
30
1. FC Union Berlin
20
L,D,L,L,W
29
VfB Stuttgart
20
L,W,L,L,D
25
SV Werder Bremen
19
D,W,L,W,D
22
TSG 1899 Hoffenheim
20
L,L,W,W,D
22
FC Augsburg
20
L,L,W,L,L
22
1. FC Köln
20
W,W,L,W,D
21
Hertha BSC
20
L,L,L,L,D
17
DSC Arminia Bielefeld
19
L,L,W,D,W
17
1. FSV Mainz 05
20
W,L,W,L,D
13
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.
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.
{{% 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
FC Bayern München
48
🍺🍺🍺🍺 top of the league
🍺🍺🍺🍺 5 win streak
🍺🍺🍺 strikers are on form
11
Jubilant: FC Bayern München are on a roll
RB Leipzig
41
🍺🍺 strong league position
🍺🍺 recent wins
🍺🍺🍺 strikers are on form
7
Jubilant: RB Leipzig are on a roll
VfL Wolfsburg
38
🍺🍺 strong league position
🍺🍺 recent wins
🍺 scored more than conceded
5
Optimistic: VfL Wolfsburg are doing well
Eintracht Frankfurt
36
🍺 safe in middle of league
🍺🍺 recent wins
🍺 scored more than conceded
4
Optimistic: Eintracht Frankfurt are doing well
Bayer 04 Leverkusen
35
🍺 safe in middle of league
💩💩 more recent losses than wins
🍺 scored more than conceded
0
Ambivalent: Bayer 04 Leverkusen aren’t doing well, but hey - at least they aren’t FC Schalke 04
Borussia Dortmund
32
🍺 safe in middle of league
💩💩 more recent losses than wins
🍺 scored more than conceded
0
Ambivalent: Borussia Dortmund aren’t doing well, but hey - at least they aren’t FC Schalke 04
Borussia Mönchengladbach
32
🍺 safe in middle of league
drew last game
🍺 scored more than conceded
2
Optimistic: Borussia Mönchengladbach are doing well
SC Freiburg
30
🍺 safe in middle of league
💩 lost last game
🍺 scored more than conceded
1
Optimistic: SC Freiburg are doing well
1. FC Union Berlin
29
💩 so so season
🍺 won last game
🍺 scored more than conceded
1
Optimistic: 1. FC Union Berlin are doing well
VfB Stuttgart
25
💩 so so season
💩💩 more recent losses than wins
🍺 scored more than conceded
-2
Ambivalent: VfB Stuttgart aren’t doing well, but hey - at least they aren’t FC Schalke 04
SV Werder Bremen
22
💩 so so season
drew last game
💩 conceded more than scored
-2
Ambivalent: SV Werder Bremen aren’t doing well, but hey - at least they aren’t FC Schalke 04
TSG 1899 Hoffenheim
22
💩 so so season
drew last game
💩 conceded more than scored
-2
Ambivalent: TSG 1899 Hoffenheim aren’t doing well, but hey - at least they aren’t FC Schalke 04
FC Augsburg
22
💩 so so season
💩💩 more recent losses than wins
💩 conceded more than scored
-4
Ambivalent: FC Augsburg aren’t doing well, but hey - at least they aren’t FC Schalke 04
1. FC Köln
21
💩 so so season
🍺🍺 recent wins
💩 conceded more than scored
0
Ambivalent: 1. FC Köln aren’t doing well, but hey - at least they aren’t FC Schalke 04
Hertha BSC
17
💩💩 poor season
💩💩 no recent wins
💩 conceded more than scored
-5
Doldrums: Hertha BSC are doing poor. Now’s a good time to bad mouth the manager.
DSC Arminia Bielefeld
17
💩💩 poor season
🍺 won last game
💩💩💩 defense is a seive
-4
Ambivalent: DSC Arminia Bielefeld aren’t doing well, but hey - at least they aren’t FC Schalke 04
1. FSV Mainz 05
13
💩💩💩💩 relagation?
drew last game
💩💩💩 defense is a seive
-7
Doldrums: 1. FSV Mainz 05 are doing poor. Now’s a good time to bad mouth the manager.
FC Schalke 04
8
💩💩💩💩 relagation?
💩💩 no recent wins
💩💩💩 defense is a seive
-9
Doldrums: FC Schalke 04 are doing poorly. Avoid supporters at all costs.
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.
Source Code
---title: The FootballData package and dashboarddescription: Getting football data, then summarising a fan's mood based on thedate: 2021-01-28date-modified: 2021-01-28image: featured.pngauthor: James Blacktags: []categories: [R, Data Analysis, Package, Football]---## Table of Contents::: {.toc}:::## IntroA colleague made the following site in jest (https://coppingr.com/), as anoutgoing 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, byleveraging one of the many APIs out there that serve live or near live footballdata.This post is an example of using the package I wrote that spawned from that idea.Documentation: https://epijim.github.io/FootballData/## Setup environment``` rlibrary(FootballData)library(tidyverse)library(glue)library(emo) #devtools::install_github("hadley/emo")# table helper - switch between html vs markdowntable_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 competitionsFirst I will pull all the leagues that are in the free tier, from thisparticular API (football-data.org) and showthem on a table.```R# Get competitionscompetitions <-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 leagueNow I'll pick my local premier league (`Bundesliga`) and pull info on it's currentstandings.``` r# Get league infoleague <- 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 teamFrom the API, I can also get more details on my local team.``` rteam_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 moodsNow that I have used the data prep functions, I can jump into the 'fun' bitof this package. Using the _live data_ I can run simple rules to try andfigure out what the mood of a supporter may be right now.Below - I run the mood logic on the Bundesliga.``` remoji_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 seeall 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 appNot interested in the Bundesliga? I also wrapped the package in a shiny appavailable 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).