Fantasy Premier League (FPL) is the official fantasy football game run by the Premier League. It has more than 7 million teams playing this season.
The chances of winning the overall game with so many players are slim indeed.
More realistic and in many ways more fun is to join a ‘mini-league’ of friends, family or co-workers and aim to win bragging rights there instead.
FPL has an API containing data on teams and players but curiously it seems to keep rather quiet about it. Nevertheless, there exists a R package developed by Rasmus Christensen that allows you to scrape data from the API easily.
In this post I will show you how to build a chart tracking the progress of teams in your league over the course of the season. We’re going to take the current top ten players in the game and pretend they are in a super mini-league with each other.
This script works best in small mini-leagues of 5-15 players.
Here is the full code:
#installation
if (!require(remotes)) {
install.packages("remotes")
}
remotes::install_github("wiscostret/fplscrapR")
library(fplscrapR)
library(tidyverse)
#ids for each team
ids <- c(568651,1229681,933902,1908330,1734934,707074,580266,4083952,207891,1966144)
#scrape data
myleague <- lapply(ids, get_entry_season)
#add to one data frame
myleague_df <- bind_rows(myleague)
myleague_df <- arrange(myleague_df,event,overall_rank)
#work out WBW rank
myleague_df <- myleague_df %>%
dplyr::group_by(event) %>%
dplyr::mutate(week_rank = rank(overall_rank, ties.method = 'first'))
#title of league
league_title = 'Top 10 players'
#colour picker: http://tristen.ca/hcl-picker/#/hlc/15/0.81/25303A/F2ED7C
ggplot(myleague_df, aes(x = event, y = week_rank, group = name)) +
geom_line(aes(colour = name), size = 1.2) +
geom_point(aes(colour = name, size = points)) + scale_y_reverse(breaks = seq(1,14,1), labels = seq(1,14,1)) + theme_minimal() +
geom_text(data = myleague_df[myleague_df$event == max(myleague_df$event),
], aes(label = paste0(name,' (',total_points,' pts)')), vjust = -0.25, nudge_x = 2, size = 6) +
scale_x_continuous(limits = c(0,max(myleague_df$event)+3), breaks = seq(1,max(myleague_df$event),1), labels = seq(1,max(myleague_df$event),1)) +
theme(legend.position = 'none',
panel.grid.minor = element_blank(),
axis.ticks.y = element_blank(),
plot.title = element_text(size = 28),
plot.subtitle = element_text(size = 20),
axis.title = element_text(size = 16),
axis.text = element_text(size = 18)) +
labs(title = paste0(league_title, ' league'), x = 'Gameweek', y = 'Rank', subtitle = paste0('Gameweek ',max(myleague_df$event))) +
scale_colour_manual(values = c('#25303A','#2A3D46','#2D4B51','#31585C','#356765','#3B756C','#448472','#509276','#60A078','#72AE79','#87BC7A','#9EC979','#B8D679','#D4E27A'))
#save png
setwd('your_directory')
ggsave(paste0('GW',max(myleague_df$event),'.png'), last_plot(), height = 10, width = 16.8)
Step 1: Install the fplscrapR package
#installation
if (!require(remotes)) {
install.packages("remotes")
}
remotes::install_github("wiscostret/fplscrapR")
library(fplscrapR)
library(tidyverse)
Step 2: Gather the ids of your league members.
ids <- c(568651,1229681,933902,1908330,1734934,707074,580266,4083952,207891,1966144)
The manual way of doing this is to open each player’s team in a browser and copy the number, which is the ID, like so:
https://fantasy.premierleague.com/entry/[COPY_THIS_ID]/event/[GAMEWEEK]
If you are using Chrome on a Windows laptop you can hover over the URL and it will show it in the bottom left corner.
Step 3: Get each player’s week-by-week performance.
myleague <- lapply(ids, get_entry_season)
The fplscrapR function we’ll need here is get_entry_season.
Here is what happens if I call this function on my own team ID:
> get_entry_season(1471904)
event points total_points rank rank_sort overall_rank bank value event_transfers event_transfers_cost
1 1 69 69 2258838 2291971 2258878 20 1000 0 0
2 2 70 139 57941 59333 229403 20 1001 0 0
3 3 39 178 4124143 4134356 673980 17 1000 1 0
4 4 64 242 1830335 1845023 483129 13 1001 0 0
5 5 60 302 1708189 1724024 337379 13 1007 0 0
6 6 72 374 643989 647701 151172 10 1008 2 0
7 7 48 422 4092123 4115978 220433 10 1013 0 0
8 8 41 463 2133068 2153517 200060 25 1017 1 0
9 9 33 496 4620731 4641639 270002 4 1019 1 0
10 10 52 548 2584563 2606363 344974 4 1021 0 0
11 11 77 625 639581 645869 189969 7 1021 1 0
12 12 88 713 12020 12489 51644 28 1022 2 0
13 13 76 785 168710 171936 33030 18 1029 2 4
14 14 59 844 1786772 1801088 21790 18 1035 0 0
15 15 58 902 1996672 2009621 25565 17 1038 1 0
16 16 67 969 1519617 1530114 22830 12 1045 1 0
points_on_bench name
1 7 Rob Grant
2 6 Rob Grant
3 11 Rob Grant
4 5 Rob Grant
5 11 Rob Grant
6 15 Rob Grant
7 3 Rob Grant
8 3 Rob Grant
9 6 Rob Grant
10 10 Rob Grant
11 6 Rob Grant
12 2 Rob Grant
13 12 Rob Grant
14 7 Rob Grant
15 21 Rob Grant
16 0 Rob Grant
It outputs a tidy data frame with my total points, overall rank, gameweek rank and other data for each week of the season.
Now we will use lapply to apply this function to all our team IDs to get all our league’s player data for the season so far.
Step 4: Convert this data into one data frame and assign week-by-week ranks
#add to one data frame
myleague_df <- bind_rows(myleague)
myleague_df <- arrange(myleague_df,event,overall_rank)
#work out WBW rank
myleague_df <- myleague_df %>%
dplyr::group_by(event) %>%
dplyr::mutate(week_rank = rank(overall_rank, ties.method = 'first'))
Calling lapply will output a list of data frames with each player’s data. Given that these data frames are identically structured we can call bind_rows to merge them into one data frame.
Next we will order it by gameweek, then by overall rank. This gets the data ordered for our more complex week-by-week rank function.
Using a combination of group_by(event) and mutate we can get R to assign a rank for each player for each gameweek. The top player that week receives 1, the next 2 and so on throughout the season so far.
Step 5: Plot the season so far
ggplot(myleague_df, aes(x = event, y = week_rank, group = name)) +
geom_line(aes(colour = name), size = 1.2) +
geom_point(aes(colour = name, size = points)) + scale_y_reverse(breaks = seq(1,14,1), labels = seq(1,14,1)) + theme_minimal() +
geom_text(data = myleague_df[myleague_df$event == max(myleague_df$event),
], aes(label = paste0(name,' (',total_points,' pts)')), vjust = -0.25, nudge_x = 2, size = 5) +
scale_x_continuous(limits = c(0,max(myleague_df$event)+3), breaks = seq(1,max(myleague_df$event),1), labels = seq(1,max(myleague_df$event),1)) +
theme(legend.position = 'none',
panel.grid.minor = element_blank(),
axis.ticks.y = element_blank(),
plot.title = element_text(size = 28),
plot.subtitle = element_text(size = 20),
axis.title = element_text(size = 16),
axis.text = element_text(size = 14)) +
labs(title = paste0(league_title, ' league'), x = 'Gameweek', y = 'Rank', subtitle = paste0('Gameweek ',max(myleague_df$event))) +
scale_colour_manual(values = c('#25303A','#2A3D46','#2D4B51','#31585C','#356765','#3B756C','#448472','#509276','#60A078','#72AE79','#87BC7A','#9EC979','#B8D679','#D4E27A'))
ggsave(paste0('GW',max(myleague_df$event),'.png'), last_plot(), height = 10, width = 16.8)
The plot is a line chart with points representing each week, using geom_line and geom_point. I linked the size aesthetic of the points to the number of Fantasy points a manager scored in a particular week. A larger circle means more points that week.
However, the points score doesn’t take into account deductions for additional transfers, so it’s not always 100% accurate. For example, in the 12th week Nick Tanner took a 12-point hit in his transfers. Even though he outscored Matthew Troha that week on paper, the -12 meant that Troha overtook him despite getting 79 points himself.
I’ve added geom_text labels for the players, with their total points in brackets. Initially my plot was covered with labels for each point on the chart. This line of code limits the labels to the most recent gameweek (event) so far.
geom_text(data = myleague_df[myleague_df$event == max(myleague_df$event),
]
I found this colour picker very useful. It’s easy to select a range of colours that are similar to one another. We need colours to help distinguish the lines from each other but if the colours are too distinct then it ends up being a distraction rather than a compliment to the chart.
The chart here is reproducible. The title, subtitle and scale will shift to accommodate more gameweeks in the future.
Analysis
At the top, Nick Tanner is the first player to pass 1,100 points for the season, regaining top spot after a poor last two weeks by his standards. Hot on his heels are Raushan Uttamchandani and Matthew Trolla.
Incredibly in fourth place we have Magnus Carlsen. The Norwegian is the current World Chess Champion and isn’t bad at FPL either, sitting pretty in fourth, ahead of Kenneth Herlihy.
The mysterious . . has risen to ninth place ahead of David Croley.
Conclusion
As I said earlier, this chart is reproducible. You can use it every week to track your league’s progress. I don’t know why FPL don’t make it a feature in the game itself, but since they haven’t, here is how to follow every twist and turn in your FPL mini-league.