The FootballData package and dashboard

R
Data Analysis
Package
Football
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.

Documentation: https://epijim.github.io/FootballData/

Setup environment

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.

# 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
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 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
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.

Below - I run the mood logic on the Bundesliga.

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
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.