<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Joey O'Brien</title><link>https://obrienjoey.github.io/</link><atom:link href="https://obrienjoey.github.io/index.xml" rel="self" type="application/rss+xml"/><description>Joey O'Brien</description><generator>Source Themes Academic (https://sourcethemes.com/academic/)</generator><language>en-us</language><copyright>© 2026 Joey O'Brien</copyright><lastBuildDate>Sun, 05 Jul 2026 00:00:00 +0000</lastBuildDate><image><url>https://obrienjoey.github.io/img/og-banner.png</url><title>Joey O'Brien</title><link>https://obrienjoey.github.io/</link></image><item><title>LSEG Ahead of the Curve Podcast: Agentic AI in Quantitative Risk Management</title><link>https://obrienjoey.github.io/talk/20260705_podcast_agentic_ai/</link><pubDate>Sun, 05 Jul 2026 00:00:00 +0000</pubDate><guid>https://obrienjoey.github.io/talk/20260705_podcast_agentic_ai/</guid><description/></item><item><title>Speaking at LSEG Quant Summit London 2026</title><link>https://obrienjoey.github.io/post/20260511_lseg_quant_summit_london/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://obrienjoey.github.io/post/20260511_lseg_quant_summit_london/</guid><description>&lt;p>On May 11, 2026, I spoke at the &lt;strong>LSEG Quant Summit London 2026&lt;/strong> held in Paternoster Square, London. The event focused on the latest developments in the Open-Source Risk Engine (ORE) ecosystem.&lt;/p>
&lt;p>I presented on two key areas during the summit:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>ORE in the Era of Agentic AI&lt;/strong>
This presentation explored how Large Language Models (LLMs) and AI agents are transforming quantitative finance. Key topics covered included:&lt;/p>
&lt;ul>
&lt;li>The capabilities and limitations of modern LLMs in writing pricing and risk models.&lt;/li>
&lt;li>Real-world use cases of AI agents automating workflows and creating value within the ORE framework.&lt;/li>
&lt;li>The integration of the Model Context Protocol (MCP) to connect AI agents directly with specialized quantitative tools.&lt;/li>
&lt;li>LSEG&amp;rsquo;s collaborative efforts in this space, including partnerships with leading AI labs like Anthropic.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>LSEG’s Internal Adoption of ORE&lt;/strong>
This talk discussed LSEG’s internal adoption, integration, and scaling of the Open-Source Risk Engine (ORE) across its post-trade services and risk management infrastructure, showcasing the practical value of open-source analytics at scale.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>It was fantastic to connect with other quantitative researchers, developers, and risk professionals to discuss the future of open-source risk analytics, enterprise integration, and artificial intelligence.&lt;/p>
&lt;p>For more information about the summit, you can visit the &lt;a href="https://solutions.lseg.com/Quant_Summit_London_2026">LSEG Quant Summit London 2026 event page&lt;/a>.&lt;/p></description></item><item><title>LSEG’s Internal Adoption of ORE</title><link>https://obrienjoey.github.io/talk/20260511_lseg_quant_summit_london_ore_adoption/</link><pubDate>Mon, 11 May 2026 00:00:00 +0000</pubDate><guid>https://obrienjoey.github.io/talk/20260511_lseg_quant_summit_london_ore_adoption/</guid><description/></item><item><title>ORE in the Era of Agentic AI</title><link>https://obrienjoey.github.io/talk/20260511_lseg_quant_summit_london/</link><pubDate>Mon, 11 May 2026 00:00:00 +0000</pubDate><guid>https://obrienjoey.github.io/talk/20260511_lseg_quant_summit_london/</guid><description/></item><item><title>ORE &amp; Risk Analytics Lab Tokyo</title><link>https://obrienjoey.github.io/talk/20251101_tokyo_ore_lab/</link><pubDate>Sat, 01 Nov 2025 00:00:00 +0000</pubDate><guid>https://obrienjoey.github.io/talk/20251101_tokyo_ore_lab/</guid><description/></item><item><title>LSEG Ahead of the Curve Podcast: UK Model Risk Regulatory Landscape &amp; SS1/23</title><link>https://obrienjoey.github.io/talk/20250115_podcast_ss123/</link><pubDate>Wed, 15 Jan 2025 00:00:00 +0000</pubDate><guid>https://obrienjoey.github.io/talk/20250115_podcast_ss123/</guid><description/></item><item><title>LSEG Ahead of the Curve Podcast: 2024 Year in Review</title><link>https://obrienjoey.github.io/talk/20241215_podcast_year_in_review/</link><pubDate>Sun, 15 Dec 2024 00:00:00 +0000</pubDate><guid>https://obrienjoey.github.io/talk/20241215_podcast_year_in_review/</guid><description/></item><item><title>LSEG Ahead of the Curve Podcast: Counterparty Credit Risk using ORE</title><link>https://obrienjoey.github.io/talk/20240715_podcast_ccr_ore/</link><pubDate>Mon, 15 Jul 2024 00:00:00 +0000</pubDate><guid>https://obrienjoey.github.io/talk/20240715_podcast_ccr_ore/</guid><description/></item><item><title>Backtesting Future Risk Factors Case Study</title><link>https://obrienjoey.github.io/publication/backtesting/</link><pubDate>Fri, 15 Mar 2024 00:00:00 +0000</pubDate><guid>https://obrienjoey.github.io/publication/backtesting/</guid><description/></item><item><title>Persistence of chimera states and the challenge for synchronization in real-world networks</title><link>https://obrienjoey.github.io/publication/chimera/</link><pubDate>Sun, 14 Jan 2024 00:00:00 +0000</pubDate><guid>https://obrienjoey.github.io/publication/chimera/</guid><description/></item><item><title>Backtesting Future Risk Factors</title><link>https://obrienjoey.github.io/talk/20231101_acadia_quant_summit_ny/</link><pubDate>Wed, 01 Nov 2023 00:00:00 +0000</pubDate><guid>https://obrienjoey.github.io/talk/20231101_acadia_quant_summit_ny/</guid><description/></item><item><title>spRingsteen</title><link>https://obrienjoey.github.io/portfolio/springsteen/</link><pubDate>Wed, 04 May 2022 00:00:00 +0000</pubDate><guid>https://obrienjoey.github.io/portfolio/springsteen/</guid><description>&lt;p>We all love him &amp;mdash; if you disagree you just haven&amp;rsquo;t spent long enough listening &amp;mdash; it is of course the Boss, the Jersey Devil, Bruce Springsteen. With a musical career spanning over fifty years, record breaking albums, an award-winning one-man Broadway show, podcast series with ex-Presidents and so much more. However what Bruce (I hope he is okay with me working on first name terms) is most famous for is his live performances. Hard rocking four hour sets, solo acoustic shows, country swing bands, the list goes on and on but throughout his career there has been a huge collection of performances with ever-changing setlists working there way through both his own recording history and also the classics from other artists.&lt;/p>
&lt;p align="center">
&lt;img src="https://media.giphy.com/media/H89Osb5ZHntuCJHFRE/giphy.gif" alt="animated" />
&lt;/p>
&lt;p>Thankfully, this huge repertoire of performances have been carefully collected by the devoted followers on E Street through the fan site &lt;a href="http://brucebase.wikidot.com/">Brucebase&lt;/a>. Within this platform is a huge collection of data describing the majority of shows the Boss has ever performed including ever song performed at each gig. This source of data is what makes up the spRingsteen &lt;code>R&lt;/code> package which I have created to allow for the data inclined to analyse both quickly and in a reproducible manner. In this post I discuss the data collection alongside its storage in a SQL setting, the process of wrapping the data in an &lt;code>R&lt;/code> package and a quick demonstration of some possible data analysis that can be performed with it.&lt;/p>
&lt;h3 id="data-collection-and-database-storage">Data collection and database storage&lt;/h3>
&lt;p>As mentioned before, the goldmine needed for this project is Brucebase - a fan led platform containing a huge collection of data describing the Springsteen career over the past 50 years. Given that this data is readily available via this online platform it can be collected rather quickly via web-scraping. This is the idea of setting up a computer program to literally scrape, or collect, the content from a website and store it in an accessible manner.&lt;/p>
&lt;p>The main idea is to find a systematic approach towards accessing the entire corpus of setlists, thankfully the maintainers of the site structure the URL associated with each page such that the date and venue of the concert is the main identifier, for example &lt;a href="http://brucebase.wikidot.com/gig:2016-05-27-croke-park-dublin-ireland">http://brucebase.wikidot.com/gig:2016-05-27-croke-park-dublin-ireland&lt;/a>. As such, if the date and location of each performance is known the setlists can then be readily accessed. Fortunately, there are separate pages (found &lt;a href="http://brucebase.wikidot.com/stats:tour-statistics">here&lt;/a>) which simply contain these exact values, so the first thing is to collect these values which then feed the setlist collector. Using the &lt;code>R&lt;/code> packages &lt;code>rvest&lt;/code> and &lt;code>dplyr&lt;/code> this can be done rather quickly.&lt;/p>
&lt;p>It is important to note however that there is quite a lot of data here, with over 50 years of performances, 25 tours, and over 1,500 songs. As such the storage of the data is important, classical spreadsheets can of course be used however more standard approaches for such data, particularly given that there are multiple tables (setlists, song details, venues and dates) is in a database setting. This is where the &lt;code>DBI&lt;/code> and &lt;code>RSQLite&lt;/code> packages comes into play where we can perform work with reading/writing a database directly in &lt;code>R&lt;/code>.&lt;/p>
&lt;pre>&lt;code class="language-r">### we connect to a database like so
db_loc = here('database/springsteen_data.sqlite')
springsteen_db = DBI::dbConnect(RSQLite::SQLite(), db_loc)
### concert_df would be a tibble containing the collected data
### which we now write to the &amp;quot;concerts&amp;quot; table in the database
DBI::dbWriteTable(springsteen_db,
&amp;quot;concerts&amp;quot;,
concert_df)
### and finally close the database connection
DBI::dbDisconnect(springsteen_db)
&lt;/code>&lt;/pre>
&lt;p>So we can collect all the data we need from Brucebase, parse it in a tidy format and write to a database all within &lt;code>R&lt;/code>, what a language! This is exactly what happens in the &lt;a href="https://github.com/obrienjoey/springsteen_db">&lt;strong>springsteen_db&lt;/strong>&lt;/a> repository hosted on my Github. There is also some very nice automation hosted within there such that the scripts are run every day to check if the Boss has had any new shows and if so that data also gets added to the database.&lt;/p>
&lt;h3 id="creating-a-data-centric-r-package">Creating a data-centric R package&lt;/h3>
&lt;p>Now with the data in place and neatly stored within a repository anyone can quickly load it into their programming language of choice, however I thought it would be particularly nice if it was accessible directly in &lt;code>R&lt;/code> via its very own package. In order to perform such development work we are extremely fortunate in the &lt;code>R&lt;/code>-universe to have packages like &lt;code>usethis&lt;/code> which take away a lot of the pain from package creation with a huge array of tools. The main emphasis - as any data scientist/engineer has no doubt been told by their software developer peers (if fortunate enough to work alongside some) - is documentation. Each dataset in my case, and how it originated, has to be described in detail alongside toy examples to demonstrate possible use cases of the data.&lt;/p>
&lt;p>Indeed, &lt;code>usethis&lt;/code> also offers functionality that allows for the package to be based upon a Github repository for future maintenance and also allows for it to be accessed in its latest form via&lt;/p>
&lt;pre>&lt;code class="language-r">remotes::install_github(&amp;quot;obrienjoey/spRingsteen&amp;quot;)
&lt;/code>&lt;/pre>
&lt;p>However for a package to be really picked up I believe it needs to be hosted on CRAN, the official &lt;code>R&lt;/code> package platform. Again, &lt;code>usethis&lt;/code> offers a range of functionality to prepare the new package for submission, where it goes through extensive validation and testing on a range of environments from the team there. All going well with documentation and the package performing correctly within the different tests on CRAN the work will get accepted and have its very own page &lt;a href="https://cran.r-project.org/web/packages/spRingsteen/index.html">like so&lt;/a>.&lt;/p>
&lt;h3 id="example-analysis">Example analysis&lt;/h3>
&lt;p>So having collected the data and developed a package we are now left with the fun part - playing with the data! So let&amp;rsquo;s load in the package directly from CRAN and have a look at some example data analysis we can do straight away. For example let&amp;rsquo;s check out the top 10 most played songs by Springsteen through out his career:&lt;/p>
&lt;pre>&lt;code class="language-r">library(spRingsteen)
spRingsteen::setlists |&amp;gt;
count(song, sort = TRUE) |&amp;gt;
slice(1:10) |&amp;gt;
mutate(song = ifelse(song == 'Born In The U.s.a.', 'Born In The U.S.A.', song),
song = forcats::fct_rev(forcats::fct_inorder(song))) |&amp;gt;
ggplot(aes(x = n, y = song)) +
geom_col(fill = '#2176FF') +
geom_text(aes(label = n),
hjust = 0, nudge_x = .5,
size = 4, fontface = &amp;quot;bold&amp;quot;, family = &amp;quot;Fira Sans&amp;quot;) +
coord_cartesian(clip = &amp;quot;off&amp;quot;) +
scale_x_continuous(expand = c(.01, .01)) +
theme_void() +
theme(
axis.text.y = element_text(size = 14, hjust = 1, family = &amp;quot;Fira Sans&amp;quot;),
plot.margin = margin(15, 30, 15, 15)
)
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="staticunnamed-chunk-3-1.png" width="672" style="display: block; margin: auto;" />&lt;/p>
&lt;h3 id="conclusions">Conclusions&lt;/h3>
&lt;p>I hope this summary gave you a nice insight into the &lt;strong>spRingsteen&lt;/strong> package (and hopefully has you inspired for a quick check in on the Boss). Overall it is a dense data engineering project involving setting up an automated scraper that can collect all the data needed, storage in a SQL database, and a nice wrapping in a &lt;code>R&lt;/code> package. All of the code can be found on my Github and hopefully it can help in setting up similar pipelines, because remember - you can&amp;rsquo;t start a fire without a spark.&lt;/p></description></item><item><title>COVID Northern Ireland</title><link>https://obrienjoey.github.io/portfolio/covid_ni/</link><pubDate>Tue, 22 Feb 2022 00:00:00 +0000</pubDate><guid>https://obrienjoey.github.io/portfolio/covid_ni/</guid><description>&lt;p>One of the many scientific benefits that have occurred since the start of the Coronavirus 19 pandemic is the emergence of large-scale open access data describing the dynamics of the virus at a range of scales. Governments, health organizations, and many others have produced data at a unparalleled rate to help society understand how the disease has rampaged through society.&lt;/p>
&lt;p>This has offered a unique opportunity for data analysts, engineers, scientists, or even simply enthusiasts, to work with real world data that is constantly evolving as the pandemic unfolds. There are great examples of storytelling that have emerged from this occurrence, for example the &lt;em>flatten the curve&lt;/em> narrative which was so prevalent in the early stages all the way up to more recent information regarding vaccination uptake globally.&lt;/p>
&lt;p>I myself have been fortunate to have been involved in numerous efforts in utilizing these novel datasets, for example early data studies regarding mobility data (provided by Apple and Google) among citizens in Ireland were a particular entertaining aspect for me as virus begun to spread, see for example this &lt;a href="https://ecmiindmath.org/2020/07/01/covid-19-and-the-irish-routine/">invited blog post&lt;/a> on Irish mobility) or some &lt;a href="%5Bhttps://twitter.com/obrienj_/status/1253248715940380677%5D">visuals&lt;/a> which picked up national attention.&lt;/p>
&lt;p>More recently I have focused on the affect of the pandemic in Northern Ireland. The work on which will be the main discussion point of this post.&lt;/p>
&lt;h3 id="data-collection">Data Collection&lt;/h3>
&lt;p>Firstly, like any good data scientist I had to ask the question - where is the data? It turns out that the &lt;a href="https://www.health-ni.gov.uk/publications/daily-dashboard-updates-covid-19-november-2021">Department of Health NI&lt;/a> produce a fantastic summary of the main metrics describing the disease including, cases, testing, hospitilizations, and deaths arising from Covid-19 on a daily-ish basis (more on this in a moment). Unfortunately, for those of us who want to access data as quickly and cleanly as possible, the data is stored in rather clunky &lt;em>.xlsx&lt;/em> spreadsheets with each tab representing a different summary.&lt;/p>
&lt;ol>
&lt;li>Look for the latest spreadsheet on a daily basis.&lt;/li>
&lt;li>Extract the needed info from each of the sheets of the .xlsx file individually.&lt;/li>
&lt;li>Clean this data in a more usable format.&lt;/li>
&lt;li>Store the data in a public repository for others to use.&lt;/li>
&lt;/ol>
&lt;p>This is exactly the framework introduced in the &lt;a href="https://github.com/obrienjoey/covid19northernireland">&lt;strong>covid19northernireland&lt;/strong>&lt;/a> repo hosted on my GitHub. The main point of this code is to first of all collect the latest spreadsheet from the online platform, clean and parse the data into a tidy format, and lastly store the data. Nicely, due to the repetitive nature of this task I could set up a server which would do this for me automatically each night and produce the output observed in the repo. See &lt;a href="https://www.joeyobrien.ie/post/20220130_covid19ni_data/">this post&lt;/a> for a detailed discussion on how this entire package works.&lt;/p>
&lt;h3 id="dashboard">Dashboard&lt;/h3>
&lt;p>Okay, so the data has been collected in a great format which is very easy to work with. Of course there are a huge number of things one could do here (epidemic modelling, spatial statistics,&amp;hellip;) but I really wanted to learn about developing dashboards in &lt;code>R&lt;/code> so that&amp;rsquo;s what I&amp;rsquo;ll write about now (to see the finished product check &lt;a href="https://obrienjoey.github.io/covidni_dashboard/">&lt;strong>here&lt;/strong>&lt;/a>).&lt;/p>
&lt;p>After some initial research I came across the &lt;a href="https://rstudio.github.io/flexdashboard/index.html">&lt;code>flexdashboard&lt;/code>&lt;/a> package which looked to be perfect for what I wanted to do. In essence it is entirely described in a &lt;code>.Rmarkdown&lt;/code> document so it is generally okay to work with, plus can look quite fantastic if given a bit of love and care (and help from the &lt;code>plotly&lt;/code> package!), for example here&amp;rsquo;s one page of the finished dashboard.&lt;/p>
&lt;p>&lt;img src="covid_ni_dashboard.PNG" alt="The Covid19 Northern Ireland Dashboard">&lt;/p>
&lt;p>For some more specifics about how it works, it makes use of a number of the cleaned datasets from the first part of this post to extract information about tests, cases, and deaths at an number of time and spatial scales. For example it was possible to load in the data at an electoral level to see how the number of cases varied by region using the &lt;code>mapview&lt;/code> package:&lt;/p>
&lt;pre>&lt;code class="language-r">`%&amp;gt;%` &amp;lt;- magrittr::`%&amp;gt;%`
### Pulling most recent data from Github
national_df &amp;lt;- data.table::fread(&amp;quot;https://raw.githubusercontent.com/obrienjoey/covid19northernireland/main/data/ni_covid_national.csv&amp;quot;)
last_national_data &amp;lt;- national_df %&amp;gt;%
tidyr::drop_na() %&amp;gt;%
dplyr::filter(date == max(date))
local_df &amp;lt;- data.table::fread(&amp;quot;https://raw.githubusercontent.com/obrienjoey/covid19northernireland/main/data/ni_covid_local.csv&amp;quot;)
shapefile &amp;lt;- sf::st_read(
&amp;quot;shapefiles/OSNI_Open_Data_-_Largescale_Boundaries_-_Local_Government_Districts_(2012).shp&amp;quot;, quiet = TRUE)
local_df_summary &amp;lt;- local_df %&amp;gt;%
dplyr::distinct() %&amp;gt;%
dplyr::mutate_if(is.numeric, ~replace(., is.na(.), 0)) %&amp;gt;%
dplyr::group_by(area) %&amp;gt;%
dplyr::summarise(tests_1 = sum(tail(tests,1)),
tests_7 = sum(tail(tests,7)),
tests_30 = sum(tail(tests,30)),
tests_all = sum(tests),
cases_1 = sum(tail(cases,1)),
cases_7 = sum(tail(cases,7)),
cases_30 = sum(tail(cases,30)),
cases_all = sum(cases),
deaths_1 = sum(tail(deaths,1)),
deaths_7 = sum(tail(deaths,7)),
deaths_30 = sum(tail(deaths,30)),
deaths_all = sum(deaths)) %&amp;gt;%
janitor::adorn_totals(&amp;quot;row&amp;quot;) %&amp;gt;%
dplyr::as_tibble() %&amp;gt;%
dplyr::filter(area != 'Missing Postcode') %&amp;gt;%
tidyr::drop_na() %&amp;gt;%
tidyr::pivot_longer(cols = tests_1:deaths_all,
names_to = 'category') %&amp;gt;%
tidyr::separate(category,
into = c(&amp;quot;Category&amp;quot;, &amp;quot;Time&amp;quot;),
sep=&amp;quot;_(?=[^_]+$)&amp;quot;)
map_df &amp;lt;- local_df_summary %&amp;gt;%
dplyr::filter(Time == 1,
Category == 'cases') %&amp;gt;%
dplyr::inner_join(shapefile, .,
by = c('LGDNAME' = 'area'))
mapview::mapview(sf::st_zm(map_df),
zcol = c('value'),
layer.name = 'Cases',
popup = FALSE)
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="map.PNG" alt="The Covid19 Northern Ireland Dashboard">&lt;/p>
&lt;p>The main problem then was that the underlying of this data updates daily as discussed before. However given the data pipeline is already set up, a similar server can be utilized to automatically update the dashboard after the original collection script has run.&lt;/p>
&lt;h3 id="conclusions">Conclusions&lt;/h3>
&lt;p>So there we have it, a discussion on developing a pipeline to automatically update and collect data (which I had a lot of fun doing, maybe I should think about a career in data engineering&amp;hellip;) and also creating a number of visualizations to create a story regarding the pandemic&amp;rsquo;s evolution. Wrapping these altogether in a neat &lt;a href="https://obrienjoey.github.io/covidni_dashboard/">dashboard&lt;/a> which I particularly like!&lt;/p></description></item><item><title>Building a tidy data pipeline: Covid19 Northern Ireland</title><link>https://obrienjoey.github.io/post/20220130_covid19ni_data/</link><pubDate>Sun, 30 Jan 2022 00:00:00 +0000</pubDate><guid>https://obrienjoey.github.io/post/20220130_covid19ni_data/</guid><description>
&lt;script src="https://obrienjoey.github.io/post/20220130_covid19ni_data/index_files/header-attrs/header-attrs.js">&lt;/script>
&lt;p>One of the many scientific benefits that have occurred since the start of the Coronavirus pandemic is the emergence of large-scale open access data describing the dynamics of the virus at a range of scales. Governments, health organizations, and many others have produced data at a unparalleled rate to help society understand how the disease has rampaged through society.&lt;/p>
&lt;p>Of course the quality of production of this data varies greatly depending on the producer. Ideally for those interested in data analytics/science the data would be readily available in a clean and tidy format which allows quick analysis to be done. Such ideas are generally considered by data engineers (which I, unfortunately, wouldn’t count myself as) to produce pipelines to collect, clean, and update the data on a regular basis to keep up with the latest figures.&lt;/p>
&lt;p>Motivated by this, I have produced a framework to consider such data describing the pandemic in the case of Northern Ireland. The &lt;a href="https://www.health-ni.gov.uk/publications/daily-dashboard-updates-covid-19-november-2021">Department of Health NI&lt;/a> produce a fantastic summary of the main metrics describing the disease including, cases, testing, hospitilizations, and deaths arising from Covid-19 on a daily-ish basis (more on this in a moment). Unfortunately, for those of us who want to access data as quickly and cleanly as possible, the data is stored in rather clunky &lt;em>.xlsx&lt;/em> spreadsheets with each tab representing a different summary.&lt;/p>
&lt;p>So the aim of this post is to describe how one can produce a pipeline to&lt;/p>
&lt;ol style="list-style-type: decimal">
&lt;li>Look for the latest spreadsheet on a daily basis.&lt;/li>
&lt;li>Extract the needed info from each of the sheets of the .xlsx file individually.&lt;/li>
&lt;li>Clean this data in a more usable format.&lt;/li>
&lt;li>store the data in a public repository for others to use.&lt;/li>
&lt;/ol>
&lt;p>This is exactly the framework introduced in the &lt;a href="https://github.com/obrienjoey/covid19northernireland">&lt;strong>covid19northernireland&lt;/strong>&lt;/a> repo hosted on my github. Let me now talk you through the main workings of the software and how the individual codes work.&lt;/p>
&lt;p>First let’s load the required packages in &lt;code>R&lt;/code>.&lt;/p>
&lt;pre class="r">&lt;code>library(tidyverse) # for data wrangling
library(janitor) # for cleaning dataframes
library(padr) # dealing with missing values
library(zoo) # time series handling
library(readxl) # reading xlsx workbooks
library(httr) # for storing downloaded files temporarily&lt;/code>&lt;/pre>
&lt;p>First of all, the spreadsheets are stored at a URL with the following form:&lt;/p>
&lt;p>&lt;a href="https://www.health-ni.gov.uk/sites/default/files/publications/health/doh-dd-19-november-2021.xlsx" class="uri">https://www.health-ni.gov.uk/sites/default/files/publications/health/doh-dd-19-november-2021.xlsx&lt;/a>&lt;/p>
&lt;p>so we will need to provide the current date to build the URL, however the files are also only updated on weekdays so we can use a little function to correct for that and then save the file lcoally for analysis as follows&lt;/p>
&lt;pre class="r">&lt;code>most_recent_weekday &amp;lt;- function(date){
# function to take a date input and return the most recent weekday
if(weekdays(date) == &amp;quot;Saturday&amp;quot;){
return(date - 1)
}else if(weekdays(date) == &amp;quot;Sunday&amp;quot;){
return(date - 2)
}else{
return(date)
}
}
data_date = format(most_recent_weekday(Sys.Date()), &amp;quot;%d%m%y&amp;quot;)
fname = paste0(&amp;#39;https://www.health-ni.gov.uk/sites/default/files/publications/health/doh-dd-&amp;#39;, data_date,&amp;#39;.xlsx&amp;#39;)
# download the file and store a connection to it in xls_file
httr::GET(fname, write_disk(xls_file &amp;lt;- tempfile(fileext = &amp;quot;.xlsx&amp;quot;)))&lt;/code>&lt;/pre>
&lt;pre>&lt;code>## Response [https://www.health-ni.gov.uk/sites/default/files/publications/health/doh-dd-280122.xlsx]
## Date: 2022-01-30 21:33
## Status: 200
## Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
## Size: 3.14 MB
## &amp;lt;ON DISK&amp;gt; C:\Users\JOEYOB~1\AppData\Local\Temp\Rtmp2V42oQ\file4a6871c01034.xlsx&lt;/code>&lt;/pre>
&lt;p>Now we have a connection to the latest version of the spreadsheet stored in the variable &lt;code>xls_file&lt;/code> (thanks to the &lt;code>httr&lt;/code> package). Generally we use .csv files when interacting with smaller tabular files, however in this case it is actually an xlsx file and such it has sheets. Fortunately the &lt;code>readxl&lt;/code> packages allows us to read directly from a given sheet by providing its name (we’ll look at the &lt;em>Summary Tests&lt;/em> sheet) as follows&lt;/p>
&lt;pre class="r">&lt;code>test_df &amp;lt;- read_excel(xls_file, sheet = &amp;quot;Summary Tests&amp;quot;)
test_df&lt;/code>&lt;/pre>
&lt;pre>&lt;code>## # A tibble: 730 x 7
## Sample_Date `TOTAL TESTS` `INDIVIDUALS TESTED PO~ `ALL INDIVIDUALS T~
## &amp;lt;dttm&amp;gt; &amp;lt;dbl&amp;gt; &amp;lt;dbl&amp;gt; &amp;lt;dbl&amp;gt;
## 1 2020-01-05 00:00:00 3 0 0
## 2 2020-01-07 00:00:00 1 0 0
## 3 2020-01-11 00:00:00 1 0 0
## 4 2020-01-16 00:00:00 2 0 0
## 5 2020-01-18 00:00:00 2 0 0
## 6 2020-01-21 00:00:00 1 0 0
## 7 2020-01-22 00:00:00 1 0 0
## 8 2020-01-25 00:00:00 1 0 0
## 9 2020-01-27 00:00:00 1 0 0
## 10 2020-01-31 00:00:00 1 0 0
## # ... with 720 more rows, and 3 more variables:
## # ROLLING 7 DAY POSITIVE TESTS &amp;lt;dbl&amp;gt;, ROLLING 7 DAY INDIVIDUALS TESTED &amp;lt;dbl&amp;gt;,
## # POSITIVITY RATE PER 100K POP &amp;lt;dbl&amp;gt;&lt;/code>&lt;/pre>
&lt;p>Now if we take a look through this data we will note that its hygiene (no offence…) isn’t the best. First of all the column names have spaces, and there is nothing a data scientist dislikes more than spaces in variable names (perhaps a slight exaggeration but then again maybe not). As such the first thing we will do is use the &lt;code>janitor::clean_names()&lt;/code> function which is fantastic at repairing these troublesome naming conventions. Second we don’t want all the columns in this instance so we can use &lt;code>dplyr::select()&lt;/code> to take what we need. Furthermore, if we check the data we note that the date column &lt;em>Sample_Date&lt;/em> isn’t consistent in that some days aren’t present this is quickly fixed with the &lt;code>padr::pad()&lt;/code> function that, as the name suggests, pads the missing days. Lastly, there are some columns where those days with no values (cases, deaths,…) are represented by NA which we don’t really want to be the case as there is information in these values so that can be resolved using tools from &lt;code>dplyr&lt;/code> also. All together we have went through a whirlwind step of data cleaning to have a much tidier and user-friendly data frame as seen below&lt;/p>
&lt;pre class="r">&lt;code>test_df %&amp;gt;%
clean_names() %&amp;gt;%
select(date = sample_date,
cases = individuals_tested_positive,
tests = all_individuals_tested,
cases_per_100k = positivity_rate_per_100k_pop
) %&amp;gt;%
pad(&amp;#39;day&amp;#39;) %&amp;gt;%
mutate_if(is.numeric, funs(ifelse(is.na(.), 0, .))) %&amp;gt;%
filter(date &amp;gt;= &amp;#39;2020-03-05&amp;#39;)&lt;/code>&lt;/pre>
&lt;pre>&lt;code>## # A tibble: 694 x 4
## date cases tests cases_per_100k
## &amp;lt;dttm&amp;gt; &amp;lt;dbl&amp;gt; &amp;lt;dbl&amp;gt; &amp;lt;dbl&amp;gt;
## 1 2020-03-05 00:00:00 2 18 0.106
## 2 2020-03-06 00:00:00 2 14 0.106
## 3 2020-03-07 00:00:00 5 14 0.264
## 4 2020-03-08 00:00:00 3 18 0.158
## 5 2020-03-09 00:00:00 3 35 0.158
## 6 2020-03-10 00:00:00 5 92 0.264
## 7 2020-03-11 00:00:00 11 91 0.581
## 8 2020-03-12 00:00:00 4 72 0.211
## 9 2020-03-13 00:00:00 11 78 0.581
## 10 2020-03-14 00:00:00 10 62 0.528
## # ... with 684 more rows&lt;/code>&lt;/pre>
&lt;p>Now there is so much more data in the file that we can pull but this would turn into a rather long post if I continued describing every one of the steps but please do check out the entire code &lt;a href="https://github.com/obrienjoey/covid19northernireland/blob/main/code/00_source.R">here&lt;/a> if interested.&lt;/p>
&lt;p>The main point however is that from this collection + cleaning exercise we create four separate tidy .csv files (found &lt;a href="https://github.com/obrienjoey/covid19northernireland/tree/main/data">here&lt;/a>) which are much easier to analyse in a data science setting.&lt;/p>
&lt;p>Of course we want these files to continually update each time the corresponding spreadsheet is updated. And no, I do not want to remember to run the script each day. Therefore the best option is to set up some pipeline that will run the script automatically on a virtual machine each day. This is done using a &lt;code>.yaml&lt;/code> script that runs my script from the cloud each night at 10pm before committing the files in the aforementioned repository for use by others. The format of this script looks like this:&lt;/p>
&lt;pre class="yaml">&lt;code>name: covid_northernireland_data_update
# Controls when the action will run.
on:
schedule:
- cron: &amp;#39;00 22 * * *&amp;#39;
jobs:
CovidDataScrape:
# The type of runner that the job will run on
runs-on: windows-latest
# Load repo and install R
steps:
- uses: actions/checkout@master
- uses: r-lib/actions/setup-r@master
# Set-up R
- name: Install Packages
run: |
install.packages(&amp;#39;tidyverse&amp;#39;)
install.packages(&amp;#39;janitor&amp;#39;)
install.packages(&amp;#39;padr&amp;#39;)
install.packages(&amp;#39;zoo&amp;#39;)
install.packages(&amp;#39;readxl&amp;#39;)
install.packages(&amp;#39;httr&amp;#39;)
shell: Rscript {0}
# Run R script
- name: Get Data
run: Rscript code/01_data_update.R
# Add new files in data folder, commit along with other modified files, push
- name: Commit Files
run: |
git config --local user.name &amp;#39;obrienjoey&amp;#39;
git config --local user.email &amp;quot;mr.joeyob@gmail.com&amp;quot;
git add data/*
git commit -am &amp;quot;update NI Covid19 data $(date)&amp;quot;
git push origin main
env:
REPO_KEY: ${{secrets.GITHUB_TOKEN}}
username: obrienjoey&lt;/code>&lt;/pre>
&lt;p>So there we have it, a fully fledged workflow to collect, clean, and store the required data in a systematic and tidy manner. I hope this discussion can be useful for you in creating your own data pipelines (and make us all appreciate the godly work of data engineers more!) allowing for clean and automatic data collection for whatever project comes your way!&lt;/p></description></item><item><title>Quantifying uncertainty in a predictive model for popularity dynamics</title><link>https://obrienjoey.github.io/talk/20220126_rss_ni/2022-01-26-rss-hawkes/</link><pubDate>Wed, 26 Jan 2022 14:00:00 +0000</pubDate><guid>https://obrienjoey.github.io/talk/20220126_rss_ni/2022-01-26-rss-hawkes/</guid><description>
&lt;script src="https://obrienjoey.github.io/rmarkdown-libs/header-attrs/header-attrs.js">&lt;/script></description></item><item><title>Calibrating COVID-19 susceptible-exposed-infected-removed models with time-varying effective contact rates</title><link>https://obrienjoey.github.io/publication/covid_ireland/</link><pubDate>Mon, 22 Nov 2021 00:00:00 +0000</pubDate><guid>https://obrienjoey.github.io/publication/covid_ireland/</guid><description/></item><item><title>Memory-cognizant generalization to Simon's random-copying neutral model</title><link>https://obrienjoey.github.io/publication/simons/</link><pubDate>Sat, 23 Oct 2021 00:00:00 +0000</pubDate><guid>https://obrienjoey.github.io/publication/simons/</guid><description/></item><item><title>Data-Driven Snooker Rankings</title><link>https://obrienjoey.github.io/portfolio/snooker_rankings/</link><pubDate>Sun, 10 Oct 2021 00:00:00 +0000</pubDate><guid>https://obrienjoey.github.io/portfolio/snooker_rankings/</guid><description>
&lt;p>&lt;em>The following is a project that came from a hobby project arising from my interest in snooker and how ideas from network science can be used in helping to rank competitors in the sport. This resulted in a &lt;a href="https://academic.oup.com/comnet/article/8/6/cnab003/6161497?login=true">peer-reviewed academic article&lt;/a> during my PhD studies, a blog post for &lt;a href="https://blog.oup.com/2021/05/a-complex-networks-approach-to-ranking-professional-snooker-players/">Oxford University Press&lt;/a>, and some interesting commentary in the &lt;a href="https://drive.google.com/file/d/181W5b-JI2lXMOYw3jNGB88UrK6lsCUba/view?usp=sharing">Sunday Mail&lt;/a>!&lt;/em>&lt;/p>
&lt;p>People love ranking things. Each year we are faced with endless awards ceremonies—just think about the Oscars, Grammys, Ballon d’Or, Time Person of the Year… the list goes on and on.&lt;/p>
&lt;p>The problem with these lists is that they are rarely backed up by quantitative data, which arises from most things rarely directly competing to help us determine who exactly the best is. One area however which experiences no shortage of such data is the realm of sport. So here let’s use a combination of data science and network science to try and answer the question in the case of snooker.&lt;/p>
&lt;p>This will involve
- obtaining the data (the exact details are beyond the scope of this post but may result in some content further up the road…)
- performing an exploratory data analysis to get a feel for the underlying structure
- developing models to rank competitors in a data-driven manner&lt;/p>
&lt;p>It will also be a good chance to show some &lt;code>R&lt;/code> code that was used to perform the analysis&lt;/p>
&lt;div id="the-data" class="section level2">
&lt;h2>1. The Data&lt;/h2>
&lt;p>The data which was scraped from &lt;a href="https://cuetracker.net/">cuetracker&lt;/a> is already available on my &lt;a href="https://github.com/obrienjoey/snooker_rankings">Github&lt;/a> so let’s load this into &lt;code>R&lt;/code> straight away. There’ll be two dataframes, one describing every match played over 50 years of the sport and the other describing the details of the different tournaments in the same time period.&lt;/p>
&lt;pre class="r">&lt;code>## load the packages
options(tidyverse.quiet = TRUE)
library(tidyverse)
library(scales)
library(cowplot)
library(tidygraph)
library(igraph)
library(ggrepel)
library(Matrix)
theme_set(theme_light())
tourn_df &amp;lt;- read_csv(&amp;#39;https://raw.githubusercontent.com/obrienjoey/snooker_rankings/master/Data/final_tourn_df.csv&amp;#39;)
match_df &amp;lt;- read_csv(&amp;#39;https://raw.githubusercontent.com/obrienjoey/snooker_rankings/master/Data/final_match_df.csv&amp;#39;)&lt;/code>&lt;/pre>
&lt;/div>
&lt;div id="exploratory-data-analysis" class="section level2">
&lt;h2>2. Exploratory Data Analysis&lt;/h2>
&lt;p>Okay so now we can dive in and do some analysis on these two dataframes using the &lt;code>tidyverse&lt;/code> toolbox. First of all how many matches are in the data?&lt;/p>
&lt;pre class="r">&lt;code>match_df %&amp;gt;%
select(match_id) %&amp;gt;%
unique() %&amp;gt;%
nrow()&lt;/code>&lt;/pre>
&lt;pre>&lt;code>## [1] 47710&lt;/code>&lt;/pre>
&lt;p>And what about the actual players?&lt;/p>
&lt;pre class="r">&lt;code>match_df %&amp;gt;%
select(match_id, player_1, player_2) %&amp;gt;%
pivot_longer(2:3, values_to = &amp;#39;player&amp;#39;) %&amp;gt;%
select(player) %&amp;gt;%
unique() %&amp;gt;%
nrow()&lt;/code>&lt;/pre>
&lt;pre>&lt;code>## [1] 1221&lt;/code>&lt;/pre>
&lt;p>Lastly how many tournaments were played?&lt;/p>
&lt;pre class="r">&lt;code>tourn_df %&amp;gt;%
nrow()&lt;/code>&lt;/pre>
&lt;pre>&lt;code>## [1] 657&lt;/code>&lt;/pre>
&lt;p>Okay, that is a good start let’s consider a more technical problem and check which player’s have won the most matches&lt;/p>
&lt;pre class="r">&lt;code># find the result frequencies of each player
player_result_df &amp;lt;- match_df %&amp;gt;%
filter(walkover != TRUE) %&amp;gt;%
select(match_id, season, player_1, player_2, player_1_score, player_2_score) %&amp;gt;%
mutate(winner = if_else(player_1_score &amp;gt; player_2_score,
player_1, player_2),
loser = if_else(player_1_score &amp;gt; player_2_score,
player_2, player_1)
) %&amp;gt;%
select(match_id, winner, loser) %&amp;gt;%
pivot_longer(cols = 2:3, values_to = &amp;#39;player&amp;#39;, names_to = &amp;#39;result&amp;#39;) %&amp;gt;%
group_by(result, player) %&amp;gt;%
tally() %&amp;gt;%
ungroup() %&amp;gt;%
pivot_wider(names_from = &amp;#39;result&amp;#39;, values_from = &amp;#39;n&amp;#39;) %&amp;gt;%
mutate(loser = replace_na(loser, 0),
winner = replace_na(winner,0),
total = loser + winner) %&amp;gt;%
pivot_longer(cols = 2:4, names_to = &amp;#39;result&amp;#39;,
values_to = &amp;#39;matches&amp;#39;)
player_result_df %&amp;gt;%
filter(result == &amp;#39;winner&amp;#39;) %&amp;gt;%
select(-result) %&amp;gt;%
arrange(desc(matches)) %&amp;gt;%
slice(1:5)&lt;/code>&lt;/pre>
&lt;pre>&lt;code>## # A tibble: 5 x 2
## player matches
## &amp;lt;chr&amp;gt; &amp;lt;int&amp;gt;
## 1 John Higgins 899
## 2 Ronnie O&amp;#39;Sullivan 843
## 3 Stephen Hendry 818
## 4 Mark Williams 768
## 5 Steve Davis 761&lt;/code>&lt;/pre>
&lt;p>So there we have the top five ranked players based on the number of wins they have had during their career.&lt;/p>
&lt;p>Interestingly, there is also a temporal element to this data in that the matches each occur within a given year, let’s look at how the above values vary over time&lt;/p>
&lt;pre class="r">&lt;code># number of tournaments by season
tourn_season_plot &amp;lt;- match_df %&amp;gt;%
mutate(season = as.integer(season)) %&amp;gt;%
group_by(season) %&amp;gt;%
summarise(no_tournaments = n_distinct(tourn_id)) %&amp;gt;%
ggplot(aes(x = season, y = no_tournaments)) +
geom_bar(stat=&amp;#39;identity&amp;#39;, fill = &amp;#39;#008DA8&amp;#39;,
color = &amp;#39;black&amp;#39;) +
scale_x_continuous(expand = c(0,0), breaks = seq(1968,2020,4)) +
scale_y_continuous(expand = c(0,0)) +
theme_minimal() +
labs(x = &amp;#39;Season&amp;#39;, y = &amp;#39;Number of Tournaments&amp;#39;) +
theme(axis.line = element_line(),
axis.text=element_text(size=12),
plot.title.position = &amp;#39;plot&amp;#39;,
panel.grid.major.x = element_blank(),
legend.text=element_text(size=12),
legend.position = c(0.85,0.9),
axis.text.x = element_text(angle = 60, hjust = 1),
plot.title = element_text(size = 14, face = &amp;#39;bold&amp;#39;),
axis.title=element_text(size=14))
# number of players by season
player_season_plot &amp;lt;- match_df %&amp;gt;%
select(season, player_1, player_2) %&amp;gt;%
pivot_longer(cols = 2:3, values_to = &amp;#39;player&amp;#39;) %&amp;gt;%
mutate(season = as.integer(season)) %&amp;gt;%
group_by(season) %&amp;gt;%
summarise(no_players = n_distinct(player)) %&amp;gt;%
ggplot(aes(x = season, y = no_players)) +
geom_bar(stat=&amp;#39;identity&amp;#39;, fill = &amp;#39;#008DA8&amp;#39;,
color = &amp;#39;black&amp;#39;) +
theme_minimal() +
scale_x_continuous(expand = c(0,0), breaks = seq(1968,2020,4)) +
scale_y_continuous(expand = c(0,0)) +
labs(x = &amp;#39;Season&amp;#39;, y = &amp;#39;Number of Players&amp;#39;) +
theme(axis.line = element_line(),
axis.text=element_text(size=12),
plot.title.position = &amp;#39;plot&amp;#39;,
panel.grid.major.x = element_blank(),
legend.text=element_text(size=12),
legend.position = c(0.85,0.9),
axis.text.x = element_text(angle = 60, hjust = 1, size = 10),
plot.title = element_text(size = 14, face = &amp;#39;bold&amp;#39;),
axis.title=element_text(size=14))
cowplot::plot_grid(tourn_season_plot, player_season_plot)&lt;/code>&lt;/pre>
&lt;p>&lt;img src="snooker_rankings_files/figure-html/unnamed-chunk-6-1.png" width="672" height="50px" style="display: block; margin: auto;" />&lt;/p>
&lt;pre class="r">&lt;code># plot the probability distribution of results obtained by each player
player_result_df %&amp;gt;%
group_by(result, matches) %&amp;gt;%
summarise(n = n()) %&amp;gt;%
ungroup() %&amp;gt;%
mutate(freq = n / sum(n)) %&amp;gt;%
filter(matches != 0) %&amp;gt;%
ggplot(aes(x = matches, y = freq, col = result)) +
geom_point() +
scale_y_log10(labels=trans_format(&amp;#39;log10&amp;#39;,math_format(10^.x))) +
scale_x_log10(labels=trans_format(&amp;#39;log10&amp;#39;,math_format(10^.x))) +
labs(y = &amp;#39;Probability&amp;#39;,
x = &amp;#39;Number of Matches&amp;#39;
) +
theme_minimal() +
scale_color_manual(name = &amp;#39;&amp;#39;,
breaks = c(&amp;#39;total&amp;#39;, &amp;#39;winner&amp;#39;, &amp;#39;loser&amp;#39;),
values = c(&amp;#39;#008DA8&amp;#39;,&amp;#39;#C3627D&amp;#39;,&amp;#39;#4A904D&amp;#39;),
labels = c(&amp;#39;Total&amp;#39;, &amp;#39;Won&amp;#39;, &amp;#39;Lost&amp;#39;)) +
theme(axis.line = element_line(colour = &amp;#39;black&amp;#39;),
axis.text=element_text(size=12),
plot.title.position = &amp;#39;plot&amp;#39;,
legend.text=element_text(size=12),
legend.position = c(0.85,0.85),
plot.title = element_text(size = 14, face = &amp;#39;bold&amp;#39;),
axis.title=element_text(size=14))&lt;/code>&lt;/pre>
&lt;p>&lt;img src="snooker_rankings_files/figure-html/unnamed-chunk-7-1.png" width="672" height="100px" style="display: block; margin: auto;" />&lt;/p>
&lt;pre class="r">&lt;code>result_df &amp;lt;- match_df %&amp;gt;%
filter(walkover != TRUE) %&amp;gt;%
select(match_id, player_1, player_2, player_1_score, player_2_score) %&amp;gt;%
mutate(winner = if_else(player_1_score &amp;gt; player_2_score,
player_1, player_2),
loser = if_else(player_1_score &amp;gt; player_2_score,
player_2, player_1),
) %&amp;gt;%
select(winner, loser)&lt;/code>&lt;/pre>
&lt;/div>
&lt;div id="the-model" class="section level2">
&lt;h2>3. The Model&lt;/h2>
&lt;p>So now getting to the main aim of this post, let’s borrow some tools from network science to help figuring out an alternative way of ranking snooker players. We’ll use what is known as &lt;strong>PageRank&lt;/strong>, an algorithm originally developed by the founders of Google in ranking webpages that helped their search engine return better results!&lt;/p>
&lt;p>The main idea is to represent the sport as a &lt;strong>graph&lt;/strong>, where each of the &lt;span class="math inline">\(N\)&lt;/span> players are a node and the edges of the graph are created by considering the matches played between players. Importantly, the result of the match is important and this is captured by the &lt;strong>direction&lt;/strong> of the edge we draw, so in each match we draw the edge from loser to winner. After consider all matches in the dataset we obtain a &lt;span class="math inline">\(N \times N\)&lt;/span> matrix with entries &lt;span class="math inline">\(w_{ij}\)&lt;/span> describing the number of times player &lt;span class="math inline">\(i\)&lt;/span> has lost to player &lt;span class="math inline">\(j\)&lt;/span>.&lt;/p>
&lt;p>With this set-up we can proceed to use the PageRank algorithm to rank the players based on the &lt;strong>prestige&lt;/strong> of player &lt;span class="math inline">\(i\)&lt;/span> - &lt;span class="math inline">\(P_i\)&lt;/span>, obtained from the following set of equations (feel free to ignore this part if equations aren’t your thing)&lt;/p>
&lt;p>&lt;span class="math display">\[\begin{equation}
P_i = (1-q)\sum_j P_j \frac{w_{ji}}{k_j^{\text{out}}} + \frac{q}{N} + \frac{1-q}{N}\sum_j P_j \delta(k_j^{\text{out}})
\end{equation}\]&lt;/span>&lt;/p>
&lt;p>Importantly this algorithm considers not only how many times one player defeated another but also how frequently the defeated player themselves defeated others. In this sense each player has some associated prestige describing their quality, and in defeating a player the winner receives some of this associated prestige. This implies that a win against a strong competitor with high prestige is now more worthwhile to a player’s rank than defeating a weaker competitor.&lt;/p>
&lt;p>Okay, so no doubt there are plenty of software packages to do this calculation for us but sometimes it is nice to code up the system ourselves so let’s do that now, using the brilliant &lt;code>tidygraph&lt;/code> and &lt;code>igraph&lt;/code> packages to work with the networks.&lt;/p>
&lt;pre class="r">&lt;code>page_rank_sim = function(match_result_df){
node_list &amp;lt;- match_result_df %&amp;gt;%
gather() %&amp;gt;%
select(-key) %&amp;gt;%
unique() %&amp;gt;%
rename(label = value) %&amp;gt;%
rowid_to_column(&amp;quot;id&amp;quot;)
el &amp;lt;- match_result_df %&amp;gt;%
count(winner, loser, name = &amp;#39;weight&amp;#39;) %&amp;gt;%
rename(target = winner,
source = loser) %&amp;gt;%
left_join(., node_list, by = c(&amp;#39;source&amp;#39; = &amp;#39;label&amp;#39;)) %&amp;gt;%
rename(from = id) %&amp;gt;%
left_join(., node_list, by = c(&amp;#39;target&amp;#39; = &amp;#39;label&amp;#39;)) %&amp;gt;%
rename(to = id) %&amp;gt;%
select(to, from, weight)
snooker_net &amp;lt;- tbl_graph(nodes = node_list,
edges = el,
directed = TRUE)
adj &amp;lt;- as_adj(snooker_net, sparse = TRUE, attr = &amp;#39;weight&amp;#39;)
### PageRank algorithm
N &amp;lt;- node_list %&amp;gt;% nrow() # number of nodes in the network
P0 &amp;lt;- rep(1/N, N) # initial values
q = 0.15 # page rank factor
s_out &amp;lt;- rowSums(adj) # outdegree of the nodes
P_temp &amp;lt;- P0
thres &amp;lt;- 1e-20
error &amp;lt;- 1
iter = 1
while(error &amp;gt; thres &amp;amp; iter &amp;lt; 1e4){
temp &amp;lt;- (1-q)*((P_temp/s_out) %*% adj) + (q/N) + ((1-q)/N)*(P0 * (s_out == 0))
error &amp;lt;- max(P_temp - temp)
P_temp &amp;lt;- temp
iter &amp;lt;- iter + 1
}
prestige &amp;lt;- as.vector(temp)
result &amp;lt;- tibble(player = node_list$label, prestige = prestige,
in_strength = as.vector(colSums(adj))) %&amp;gt;%
arrange(desc(prestige)) %&amp;gt;%
mutate(rank_pre = rank(-prestige, ties.method = &amp;quot;first&amp;quot;),
rank_str = rank(-in_strength, ties.method = &amp;quot;first&amp;quot;))
result
}&lt;/code>&lt;/pre>
&lt;/div>
&lt;div id="results" class="section level2">
&lt;h2>4. Results&lt;/h2>
&lt;p>Using the approach described above, after constructing a network based upon all recorded professional snooker games, it can quickly be determined who is the greatest player of all time. Interestingly, it is not Ronnie O’Sullivan with his natural talent, Steve Davis and his distinguished trophy room, or the winning machine known as Stephen Hendry, but rather the four-time world champion John Higgins! This result may seem surprising to some snooker fans but when the data is considered it is entirely understandable. While both Davis and Hendry have plenty of trophies and wins to their name, the quality of player competing in their era was considerably less than those faced by Higgins and O’Sullivan (who is ranked the second greatest through our approach).&lt;/p>
&lt;pre class="r">&lt;code>set.seed(1)
# perform the PageRank
all_time_df &amp;lt;- page_rank_sim(result_df)
all_time_df %&amp;gt;%
filter(rank_pre &amp;lt;= 30 | rank_str &amp;lt;= 30) %&amp;gt;%
ggplot(aes(x = rank_str, y = rank_pre, label = player)) +
geom_abline(slope = 1, intercept = 0,
color=&amp;quot;red&amp;quot;,
linetype=&amp;quot;dashed&amp;quot;, size=1) +
geom_point() +
theme_minimal() +
geom_text_repel(size = 2.25) +
labs(x = &amp;#39;Rank by Wins&amp;#39;,
y = &amp;#39;Rank by PageRank&amp;#39;) +
theme(axis.line = element_line(colour = &amp;#39;black&amp;#39;),
axis.text=element_text(size=10),
plot.title.position = &amp;#39;plot&amp;#39;,
plot.title = element_text(size = 14, face = &amp;#39;bold&amp;#39;),
plot.subtitle = element_text(size = 12),
axis.title=element_text(size=12))&lt;/code>&lt;/pre>
&lt;p>&lt;img src="snooker_rankings_files/figure-html/unnamed-chunk-10-1.png" width="672" height="200px" style="display: block; margin: auto;" />&lt;/p>
&lt;/div>
&lt;div id="concluding-thoughts" class="section level2">
&lt;h2>5. Concluding Thoughts&lt;/h2>
&lt;p>In order to help satisfy the intrinsic human desire to rank entities, this work has proposed a method which utilizes a mathematical framework to determine a ranking of competitors which considers not only the number of times a player has won but also the quality associated with each win.&lt;/p>
&lt;/div></description></item><item><title>Spreading of memes on multiplex networks</title><link>https://obrienjoey.github.io/talk/2021_networks/2021-07-05-multiplex_networks/</link><pubDate>Mon, 05 Jul 2021 15:30:00 +0000</pubDate><guid>https://obrienjoey.github.io/talk/2021_networks/2021-07-05-multiplex_networks/</guid><description>
&lt;script src="https://obrienjoey.github.io/rmarkdown-libs/header-attrs/header-attrs.js">&lt;/script></description></item><item><title>Hierarchical route to the emergence of leader nodes in real-world networks</title><link>https://obrienjoey.github.io/publication/leaders/</link><pubDate>Fri, 14 May 2021 00:00:00 +0000</pubDate><guid>https://obrienjoey.github.io/publication/leaders/</guid><description/></item><item><title>A complex networks approach to ranking professional Snooker players</title><link>https://obrienjoey.github.io/post/snooker_oup/</link><pubDate>Mon, 03 May 2021 00:00:00 +0000</pubDate><guid>https://obrienjoey.github.io/post/snooker_oup/</guid><description>&lt;p>One does not have to look far to observe the innate desire us humans have to rank things. Each year we are faced with endless awards ceremonies—just think about the Oscars, Grammys, Ballon d’Or, Time Person of the Year… the list goes on and on. Indeed, at the turn of every year/decade/century media organizations fill whitespace with their “top 100 (insert domain of interest here) of (insert temporal period here)” where a supposed expert gives their thoughts on the optimal ordering of entities from the domain of choice. These rankings are, however, far from definitive, and rather than settle an argument they instead tend to simply add fuel to the fire of the debate. The main problem with these approaches is that the rankings tend to be based upon personal preference with an extremely high level of subjectivity (the player who scored the goal that won their team the cup, the movie they saw on their first date…). In an ideal science-based world, however, any ranking would be determined using an entirely objective approach which, unfortunately, requires detailed data describing the interactions between different entities to determine who/what exactly is the best.&lt;br>
While the collection of such data can prove difficult for most domains, one area in which there is no shortage of detailed historical data of competition is within the field of sport. Rather, there is in fact a data revolution taking place in sport whereby athletes are becoming increasingly aware of statistics and, moreover, devoted fans of said sports have amassed huge collections of historical results that readily allow for data-driven mathematical analysis to be conducted. Motivated by this, researchers at MACSI (Mathematics Applications Consortium for Science and Industry) based in the University of Limerick in Ireland posed the following problem—who is the greatest snooker player of all time?
For the unaware, snooker is a cue-based game in which competitors compete head-to-head by trying to strike balls on a rectangular table and obtain points, based upon the denomination of the ball, as a result of successfully landing it within one of the six “pockets” or holes on the table. Like most sports, there is constant discussion regarding which competitor is the greatest to have graced the game.
To answer the question in a quantitative manner the first thing needed was detailed data of results from the sport which were obtained via a large data scraping exercise (involving pulling statistics from multiple webpages). This resulted in a collection of the match results from over fifty years (1968-2020), amounting to 657 tournaments featuring 1,221 unique players competing in 47,710 matches.
With this data at hand, the question then becomes how best to use it in ranking the competitors. Perhaps the most obvious thing to do would be simply to count the number of times each player had won, the equivalent of giving the Oscar to the highest grossing movie—not ideal. The issue with this approach is in the fact that the player with the most wins could simply repeatedly play Joe Bloggs at the snooker hall. In order to offer an alternative approach, we make use of the network description of this data. This network is obtained by letting each player be represented by a node and for each match an edge is drawn from the losing node to the corresponding winning node.&lt;/p>
&lt;p>&lt;img src="distributions.png" alt="">
&lt;em>(a) Probability distribution of the number of matches (blue), wins (pink), and losses (green) for each professional Snooker player. Each point shows the fraction of players that had a certain number of each quantity. Note the scale of both axes are logarithmic suggesting that most players appear in very few matches while a select few appear much more often. (b) Corresponding plot but now showing the probability of having more than a certain number of matches for each player.&lt;/em>&lt;/p>
&lt;p>Figure 1 shows the corresponding probability distributions describing the number of matches, wins, and losses for each player, which appears to be highly skewed, suggesting a form of “Matthew effect” occurring in the sport in that a select few players win many games while many players win very few. Using this networked representation, an algorithm was proposed which could be used in identifying a ranking of the most important players. In particular, this algorithm considers not only how many times one player defeated another but also how frequently the defeated player themselves defeated others. In this sense each player has some associated prestige describing their quality, and in defeating a player the winner receives some of this associated prestige. This implies that a win against a strong competitor with high prestige is now more worthwhile to a player’s rank than defeating a weaker competitor.&lt;/p>
&lt;p>Using the approach described above, after constructing a network based upon all recorded professional snooker games, it can quickly be determined who is the greatest player of all time. Interestingly, it is not Ronnie O’Sullivan with his natural talent, Steve Davis and his distinguished trophy room, or the winning machine known as Stephen Hendry, but rather the four-time world champion John Higgins! This result may seem surprising to some snooker fans but when the data is considered it is entirely understandable. While both Davis and Hendry have plenty of trophies and wins to their name, the quality of player competing in their era was considerably less than those faced by Higgins and O’Sullivan (who is ranked the second greatest through our approach).&lt;/p>
&lt;p>&lt;img src="rankings.png" alt="">
&lt;em>Rankings of the top 30 Snooker players of all time based upon the level of prestige determined via the proposed algorithm (vertical axis) compared to their ranking based upon total wins (horizontal axis). The red line acts as a reference for those players who were ranked higher by prestige than wins (under the line) and vice-versa (above the line).&lt;/em>&lt;/p>
&lt;p>Moreover, this approach can give an indication as to which players have faced tougher competition in wins during their careers. This is highlighted in Figure 2 where the top 30 players ranked using the prestige approach (our algorithm) is compared to their ranking based upon the number of wins. While the two ranking schemes are clearly very correlated, a considerable amount of information may be obtained from the location of players within this graphic. Those players who are above the red line (Davis, Hendry, White…) have obtained their wins from less competitive games (i.e., their wins were accumulated against players with less prestige), while the contrary is true for those below the line, many of whom are from the modern era.
In order to help satisfy the intrinsic human desire to rank entities, this work has proposed a method which utilizes a mathematical framework to determine a ranking of competitors which considers not only the number of times a player has won but also the quality associated with each win. Excitingly, this concept offers plenty of room for extension to any domain for which detailed data on interactions is available and can hopefully help in ending the many arguments about the greatest competitor in your favourite area of interest!&lt;/p>
&lt;p>&lt;em>This article originally appeared on Oxford University Press Blog, the article can be found &lt;a href="https://blog.oup.com/2021/05/a-complex-networks-approach-to-ranking-professional-snooker-players/">here&lt;/a>.&lt;/em>&lt;/p></description></item><item><title>Sporting contests through the lens of complex systems: A data-driven approach</title><link>https://obrienjoey.github.io/talk/2021_cit_sports/2021-03-12-cit-sports/</link><pubDate>Fri, 12 Mar 2021 11:00:00 +0000</pubDate><guid>https://obrienjoey.github.io/talk/2021_cit_sports/2021-03-12-cit-sports/</guid><description>
&lt;script src="https://obrienjoey.github.io/rmarkdown-libs/header-attrs/header-attrs.js">&lt;/script></description></item><item><title>A complex networks approach to ranking professional Snooker players</title><link>https://obrienjoey.github.io/publication/snooker/</link><pubDate>Sun, 07 Mar 2021 00:00:00 +0000</pubDate><guid>https://obrienjoey.github.io/publication/snooker/</guid><description/></item><item><title>Identification of skill in an online game: The case of Fantasy Premier League</title><link>https://obrienjoey.github.io/publication/fpl/</link><pubDate>Wed, 03 Mar 2021 00:00:00 +0000</pubDate><guid>https://obrienjoey.github.io/publication/fpl/</guid><description/></item><item><title>What makes a good (fantasy) football manager? - webscraping and data analysis in R</title><link>https://obrienjoey.github.io/talk/whyr_21/2021-04-02-whyr-webscrping/</link><pubDate>Thu, 04 Feb 2021 15:00:00 +0000</pubDate><guid>https://obrienjoey.github.io/talk/whyr_21/2021-04-02-whyr-webscrping/</guid><description>
&lt;script src="https://obrienjoey.github.io/rmarkdown-libs/header-attrs/header-attrs.js">&lt;/script></description></item><item><title>A Generalization of the Classical Kelly Betting Formula to the Case of Temporal Correlation</title><link>https://obrienjoey.github.io/talk/2020_cdc_kelly/2020-12-14-cdc-kelly/</link><pubDate>Mon, 14 Dec 2020 00:00:00 +0000</pubDate><guid>https://obrienjoey.github.io/talk/2020_cdc_kelly/2020-12-14-cdc-kelly/</guid><description>
&lt;script src="https://obrienjoey.github.io/rmarkdown-libs/header-attrs/header-attrs.js">&lt;/script></description></item><item><title>O’Sullivan, Hendry, Davies,...? A complex networks approach to ranking professional Snooker players</title><link>https://obrienjoey.github.io/talk/siam_dublin_20/2020-12-04-siam-dublin-20/</link><pubDate>Fri, 04 Dec 2020 11:00:00 +0000</pubDate><guid>https://obrienjoey.github.io/talk/siam_dublin_20/2020-12-04-siam-dublin-20/</guid><description>
&lt;script src="https://obrienjoey.github.io/rmarkdown-libs/header-attrs/header-attrs.js">&lt;/script></description></item><item><title>Branching Processes and their Application to Popularity Dynamics</title><link>https://obrienjoey.github.io/talk/namur_seminar_20/2020-11-05-namur-seminar/</link><pubDate>Thu, 05 Nov 2020 12:00:00 +0000</pubDate><guid>https://obrienjoey.github.io/talk/namur_seminar_20/2020-11-05-namur-seminar/</guid><description>
&lt;script src="https://obrienjoey.github.io/rmarkdown-libs/header-attrs/header-attrs.js">&lt;/script></description></item><item><title>Anderson-like localization in real-world non-normal networks</title><link>https://obrienjoey.github.io/talk/localization_netsci20/2020-09-21-localization_netsci/</link><pubDate>Mon, 21 Sep 2020 17:40:00 +0000</pubDate><guid>https://obrienjoey.github.io/talk/localization_netsci20/2020-09-21-localization_netsci/</guid><description>
&lt;script src="https://obrienjoey.github.io/rmarkdown-libs/header-attrs/header-attrs.js">&lt;/script></description></item><item><title>Identification of skill in an online game: The case of Fantasy Premier League</title><link>https://obrienjoey.github.io/talk/qs_netsci20/2020-09-17-fpl_qs_netsci/</link><pubDate>Thu, 17 Sep 2020 16:40:00 +0000</pubDate><guid>https://obrienjoey.github.io/talk/qs_netsci20/2020-09-17-fpl_qs_netsci/</guid><description>
&lt;script src="https://obrienjoey.github.io/rmarkdown-libs/header-attrs/header-attrs.js">&lt;/script></description></item><item><title>A Generalized Framework for Simultaneous Long-Short Feedback Trading</title><link>https://obrienjoey.github.io/publication/sls/</link><pubDate>Tue, 21 Jul 2020 00:00:00 +0000</pubDate><guid>https://obrienjoey.github.io/publication/sls/</guid><description/></item><item><title>A generalization of the classical Kelly betting formula to the case of temporal correlation</title><link>https://obrienjoey.github.io/publication/kelly/</link><pubDate>Mon, 22 Jun 2020 00:00:00 +0000</pubDate><guid>https://obrienjoey.github.io/publication/kelly/</guid><description/></item><item><title>Quantifying uncertainty in a predictive model for popularity dynamics</title><link>https://obrienjoey.github.io/publication/hawkes/</link><pubDate>Wed, 03 Jun 2020 00:00:00 +0000</pubDate><guid>https://obrienjoey.github.io/publication/hawkes/</guid><description/></item><item><title>Covid-19 and the Irish Routine</title><link>https://obrienjoey.github.io/post/covid_mobility/</link><pubDate>Mon, 01 Jun 2020 00:00:00 +0000</pubDate><guid>https://obrienjoey.github.io/post/covid_mobility/</guid><description>&lt;p>The way in which the terrible virus that is Covid-19 has rampaged through the entire world over the course of 2020 has altered the behaviour of humanity in a way no one could have foreseen. With the aim of understanding the properties of this disease alongside the way in which it is (and ultimately will) spread through and affect society has resulted in a momentous collaborative effort across all areas of science from epidemiologists all the way through to economists. The community of applied mathematicians and statisticians have been conducting research at the forefront of this effort through developing compartmental models not unlike those seen in an introductory dynamical systems course and making use of a variety of datasets in order to both increase the complexity of the models and also infer the parameters needed to fit said models with the empirical incidence rate.&lt;/p>
&lt;p>The requirement for datasets to aid this scientific endeavour and inform society itself has resulted in large companies making some of their (usually vaulted!) data – generated through the digital traces left every day when interacting with technology – publicly available. Arguably the most obvious demonstration of this is the mobility data provided by two of the world’s largest corporations – Apple and Google. Both of whom are currently providing aggregated data from requests made to their corresponding map services over multiple geographical resolutions ranging from country, to county, to in some cases city levels. This offers the potential to gain an insight into just how much the daily Irish routine has been altered in the age of social distancing and travel restrictions. To demonstrate some of these insights two data sources in the case of Ireland are considered below.&lt;/p>
&lt;p>&lt;strong>Apple&lt;/strong>&lt;/p>
&lt;p>Apple have released daily country level time series data on how levels for three modes of transport – driving, walking, and public transport – changed relative to the baseline values from January 13th. One may see in the early points of the time series in Figure 1 that Irish society had a relatively consistent weekly pattern of all three modes of transport with two consecutive peaks (the weekends) followed by five more modest values describing the weekdays. The first reported case of the virus on Irish shores occurred on the 29th February but we see that it is almost two full weeks before a significant change in the transport levels occur when the Containment Phase strategy was introduced with the closure of schools and colleges on March 12th.&lt;/p>
&lt;figure>
&lt;a data-fancybox="" href="apple_mobility_ecmi.png" data-caption="Apple Mobility Data">
&lt;img data-src="apple_mobility_ecmi.png" class="lazyload" alt="" >&lt;/a>
&lt;figcaption data-pre="Figure " data-post=":" class="numbered">
Apple Mobility Data
&lt;/figcaption>
&lt;/figure>
&lt;p>The readily increasing number of cases resulted in the announcement of national travel restrictions on March 27th and on this date arguably the most interesting point in the entire time series is seen with a significant increase suggesting a possible `last chance saloon’ to travel home before restrictions came into place. One may then observe over the following two months how the country was brought to a near standstill in terms of mobility. Also, interestingly, it can be seen that Apple had missing data from the 10th and 11th of May showing how even the largest corporations can run into problems with data collection! Restrictions began to ease through the end of May as the number of cases had slowed and a roadmap of Phases were proposed with specific relaxations at each point. After Phase 1 (18th May), which involved increasing the permitted travel distance to 5km from one’s home, an increase in mobility across all modes occurs and further gains after the commencement of Phase 2 which further extended the travel distance to 20km. The levels of mobility is gradually returning to those seen prior to the start of Covid-19 as the growth in the number of cases decreases.&lt;/p>
&lt;p>&lt;strong>Google&lt;/strong>&lt;/p>
&lt;p>Similar to Apple, Google has also released aggregated time series of requests to their own map service but with a different set of descriptions. Rather than the mode of transport used above, Google released requests regarding six distinct categories – retail and recreation, grocery and pharmacy, parks, transit stations, workplaces, and residential areas. They also provided a finer level of resolution namely at a county level. Here two of the categories (residential and workplaces) at six different Thursdays over the past few months are considered.&lt;/p>
&lt;figure>
&lt;a data-fancybox="" href="works_ecmi.png" data-caption="Google Workplace Data">
&lt;img data-src="works_ecmi.png" class="lazyload" alt="" >&lt;/a>
&lt;figcaption data-pre="Figure " data-post=":" class="numbered">
Google Workplace Data
&lt;/figcaption>
&lt;/figure>
&lt;p>The set of plots in Figure 2 shows the percentage change in the frequency at which people were at a workplace from a baseline taken over January 3rd – February 6th. A considerable decrease as the restrictions were at their strongest up to the end of April is seen. In the final two maps however a return to normality is observed as the restrictions have lifted demonstrating people returning to work. One interesting aspect throughout is the level of workplace activity in Dublin. The capital city is found in the small county in the east of the island which is the darkest shade in each of the six plots, the suggestion being that workers have been slower to return to work in Dublin, with the hypothesis being that the more technical jobs found there are more amenable to ‘working-from-home’ and this development will prove interesting to follow as the country further moves out of restrictions.&lt;/p>
&lt;p>The equivalent statistics in the case of residential areas are shown in Figure 3, where the grey areas represent counties in which there wasn’t enough data to provide a statistically significant estimate. These show the opposite effect to that found in the workplace requests as to be expected with people having to stay at home if they cannot go to the workplace. But again, the gradual return to the original baseline can be seen as we move forward in time and restrictions ease suggesting a return to some form of normality for the Irish population.&lt;/p>
&lt;figure>
&lt;a data-fancybox="" href="residential_ecmi.png" data-caption="Google Residential Data">
&lt;img data-src="residential_ecmi.png" class="lazyload" alt="" >&lt;/a>
&lt;figcaption data-pre="Figure " data-post=":" class="numbered">
Google Residential Data
&lt;/figcaption>
&lt;/figure>
&lt;p>&lt;strong>Summary&lt;/strong>&lt;/p>
&lt;p>The availability of previously sensitive data offers fantastic opportunities for the applied mathematics community to provide analysis to society on the effect Covid-19 has had while also offering the potential to be included in technical models being used to describe the spreading process and how it is changing as these restriction measures fluctuate. It is also fascinating to get a small insight into the data which is available to these large corporations as a result of the use of their services and how they may be learnt from. Positively, it appears from these two separate sources that Irish daily life is slowly returning to where it was prior to the commencement of restrictions as the speed at which the virus spreads has been slowed with such great effect which one hopes to remain the case as society progresses in the future.&lt;/p>
&lt;p>&lt;em>This article originally appeared on the ECMI (European Consortium for Mathematics in Industry), the article can be found &lt;a href="https://ecmiindmath.org/2020/07/01/covid-19-and-the-irish-routine/">here&lt;/a>.&lt;/em>&lt;/p></description></item><item><title>Webscraping with R</title><link>https://obrienjoey.github.io/talk/webscraping_macsi_20/2020-02-26-webscraping_macsi/</link><pubDate>Wed, 26 Feb 2020 15:00:00 +0000</pubDate><guid>https://obrienjoey.github.io/talk/webscraping_macsi_20/2020-02-26-webscraping_macsi/</guid><description>
&lt;script src="https://obrienjoey.github.io/rmarkdown-libs/header-attrs/header-attrs.js">&lt;/script></description></item><item><title>"How many likes do you think this will get?": Predicting self-exciting processes</title><link>https://obrienjoey.github.io/talk/siam_galway_19/2020-01-11-siam-galway-19/</link><pubDate>Fri, 06 Dec 2019 00:00:00 +0000</pubDate><guid>https://obrienjoey.github.io/talk/siam_galway_19/2020-01-11-siam-galway-19/</guid><description/></item><item><title>What Makes a Good (Fantasy) Football Manager?</title><link>https://obrienjoey.github.io/talk/satrday_19/2020-01-10-satrday-19/</link><pubDate>Sat, 16 Nov 2019 00:00:00 +0000</pubDate><guid>https://obrienjoey.github.io/talk/satrday_19/2020-01-10-satrday-19/</guid><description>
&lt;script src="https://obrienjoey.github.io/rmarkdown-libs/header-attrs/header-attrs.js">&lt;/script></description></item><item><title>The maths of fantasy football</title><link>https://obrienjoey.github.io/post/fpl_brainstorm/</link><pubDate>Thu, 12 Sep 2019 00:00:00 +0000</pubDate><guid>https://obrienjoey.github.io/post/fpl_brainstorm/</guid><description>&lt;p>The Premier League is back. The season kicked off in August with last season&amp;rsquo;s runners-up Liverpool giving newly promoted Norwich City a tough introductory lesson in the form of a 4-1 defeat. The Reds' talisman Mo Salah scored their second goal, before assisting Virgil van Dijk for their third. While this performance no doubt thrilled Liverpool fans, there was also another group of people who were just as (if not more) delighted with the display. That&amp;rsquo;s the 44.7% of Fantasy Premier League managers who had Salah in their team.&lt;/p>
&lt;p>For the uninitiated, Fantasy Premier League (commonly referred to as Fantasy Football) is an online game where six million users are provided with a virtual £100 million budget to assemble a squad of 15 real-life Premier League footballers from the 20 teams in the league. Each player has two characteristics: (a) their position - goalkeeper (GK), defender (DEF), midfielder (MID), or forward (FWD), and (b) the price one must pay to have them in their team.&lt;/p>
&lt;p>The user must select two GKs, five DEFs, five MIDs and three FWDs while remaining on budget. Once the squad is ready, the user then proceeds to choose their 11 starting players, each of whom will score points as a result of their statistical performance in the physical matches they appear in with their team that Gameweek (GW). Users also nominate one of their players to be the team’s captain, which means that their point total is doubled that week.&lt;/p>
&lt;p>At the end of the GW, each team’s points are tallied and users are given a ranking, process which repeats throughout the season. The challenge of the game is to determine an optimal way of spending your budget as to maximise your points.&lt;/p>
&lt;p>&lt;img src="fpl_job.png" alt="">
&lt;em>How my team looked over the first GW of this season, giving me a total of 82 points and a measly rank of 839,678&lt;/em>&lt;/p>
&lt;p>During the off-season, researchers at &lt;a href="https://ulsites.ul.ie/macsi/">MACSI&lt;/a> in the University of Limerick undertook a large data scraping exercise to obtain the information of the top one million ranked players and their teams over the course of last season. We did so in the hope of gaining some insight into what characteristics and actions make a successful Fantasy Premier League &amp;ldquo;manager&amp;rdquo;. Specifically, we want to look at the different actions taken by those users that finished in the top 10K positions, in comparison to the moves made by the rest of the top million users.&lt;/p>
&lt;p>Let&amp;rsquo;s focus on one element of the game, namely the manager’s use of chips. No, I’m not referring to their diet, but rather the three special Gameweek chips in the game itself. These allow users to have special privileges during the week that chip is played.&lt;/p>
&lt;p>Due to the hectic nature of the English football season, there are occasions when fixtures must be rearranged known in Fantasy Football as Blank Gameweeks and Double Gameweeks.&lt;/p>
&lt;p>&lt;strong>Blank Gameweeks&lt;/strong> are GWs in which there are less than the usual ten fixtures as a result of fixture postponement. This occurred in 2017/18 in GW 27, 31 and 33 where there was only eight, five and six fixtures respectively. These weeks can make it difficult for users to field a full team of 11 playing players.&lt;/p>
&lt;p>&lt;strong>Double Gameweeks&lt;/strong> occur when teams who have fixtures postponed must play two matches in a GW. These offer prime opportunities to make extra points as players now have twice the opportunity to earn more points. Last season, these occurred in GW 32, 34 and 35, when there were a whopping 15, 11 and 14 games played.&lt;/p>
&lt;p>The most successful chip strategy from last season was the &lt;strong>Bench Boost&lt;/strong>. You choose a team of 11 starting players from your squad of 15 players and the remaining four are what are known as bench players. The point scores of these bench players don’t contribute to your GW total aside from the week in which one plays their bench boost chip. It would appear that if one’s bench players were going to receive points then the ideal time to use this chip would be when each of these players may play twice (to maximize the potential upswing from the chip) i.e., during one of the double GWs. Our analysis as seen in the following graph supports this claim:&lt;/p>
&lt;p>&lt;img src="bb_use.png" alt="">
&lt;em>Usage of the Bench Boost chip across the gameweeks last season for top 10k managers compared to the top 1 million.&lt;/em>&lt;/p>
&lt;p>You can see how a massive 79% of the players in the top 10k used the bench boost chip in GW 35 (the week of the season where there were the most fixtures). This compared to only 29% of those in the top 1 million. In fact, less than 10% of top 10k users had used the chip prior to this GW, a figure which the top 1 million surpassed by GW 14! The result? The top 10k users were much more likely to have obtained a higher point total from this chip (23.16) than the remaining players in the top 1 million (13.82).&lt;/p>
&lt;p>In Fantasy Football, users can make only one free transfer in any GW, which allows them to exchange one of their players for another player in the game (provided they can afford them). The &lt;strong>Free Hit&lt;/strong> chip allows the user to make unlimited transfers for that week. They could choose a new squad of 15 if they so pleased, but their team then reverts to how it was at the start of the week.&lt;/p>
&lt;p>There tends to be two schools of thought within the Fantasy Football community as to what is the best way to use this chip. Should one use it during a blank GW when a large number of their players might not have a game? Or target the double GWs, because they have the chance of essentially hand-picking players appearing in 22 games (if their 11 starters all have two matches)?&lt;/p>
&lt;p>&lt;img src="FH_use.png" alt="">
&lt;em>Usage of the Free Hit chip across the gameweeks last season for top 10k managers compared to the top 1 million.&lt;/em>&lt;/p>
&lt;p>Our analysis (above) looked into what was the best strategy last season. 33% of users in the top 1 million had used this chip prior to GW 27 i.e. before any of the blank/double GWs came into play, which pales with the 7% of top 10k. We found that a massive 33% of the top million used it in GW 31, the week with the fewest fixtures, in comparison with 17% of the top 10k.&lt;/p>
&lt;p>The other considerable GW which featured the chips use was GW 32, the large double Gameweek, with 69% of those who finished in the top 10k choosing to use the chip then, compared with only 15% of the remaining users. The top 10k generally received more points from this chip with a mean points value of 84.7, compared to 60.5 for the top million users. This suggests the strategy of using the chip during a large double GW was most definitely the best approach to take last season.&lt;/p>
&lt;p>Each Gameweek, the user nominates one of their players to be the team&amp;rsquo;s captain, which means that their point total is doubled that week. The &lt;strong>Triple Captain&lt;/strong> chip simply increases this to a factor of three. This is arguably the chip which offers the most uncertain returns, because not only does one have the option of which GW to use the chip but also the choice of player to give the virtual armband to.&lt;/p>
&lt;p>The temptation to use this chip during a double GW (eg Sergio Aguero for Manchester City versus Fulham and Cardiff City in GW 32) is obviously very enticing since the player would have two games from which they could return triple gains. However, if their team has two difficult fixtures it may not prove to be worthwhile, and one may instead prefer to use it during a normal single GW where the player has an appealing fixture (e.g., Salah for Liverpool versus Huddersfield Town in GW 36). A third option that is commonly mentioned is to use the chip in the final GW where many teams have nothing to play for and can result in high score-lines (e.g., Nathan Redmond for Southampton vs. Huddersfield in GW 38).&lt;/p>
&lt;p>&lt;img src="tc_use.png" alt="">
&lt;em>Usage of the Triple Captain chip across the gameweeks last season for top 10k managers compared to the top 1 million.&lt;/em>&lt;/p>
&lt;p>Investigating the data found a variety of strategies for this chip at different points in the season. Once again, we highlight the fact that while only 6% of top 10k users had used their triple captain chip by GW24, a considerable 25% of the remaining players had done so at the same point. In terms of the GW in which it was most commonly used, we found that 43% of top 10k users used it in GW 36, which interestingly was a single GW (where Liverpool had a very attractive game in terms of difficulty). 29% of the top 1 million decided that the double GW 32 was the optimal time to play the chip.&lt;/p>
&lt;p>In terms of the range of scores obtained, it appears that the triple captain chip offers the least in terms of total number of points, which in some way is to be expected as it only provides an increase for a single player. We found that the triple captain chip resulted in a mean score of 11.5 for the top 10k and 8.67 for the other group.&lt;/p>
&lt;p>For Fantasy Football managers looking for tips regarding the optimal way to use their chips, here are some suggestions from the data&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Save your chips! While it is very tempting to use a chip at an early stage in the season to get the top spot in your league, we can see from last season&amp;rsquo;s data that good users saved their chips until the last 10 weeks in the season to make the most of the blank and double GWs.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>There are generally two large double GWs and these are prime times to use your chips to maximise your returns, especially the Bench Boost. Almost 80% of top 10k managers used this strategy.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Don’t be afraid to play your Triple Captain chip in a single GW. It is very tempting to use the chip during a double GW, where the player has two opportunities to score points. But if the fixtures aren’t great for the player, it is arguably better to use it in a single GW where they have a nice fixture as shown by the top 10k using the chip in GW 36 when Liverpool played Huddersfield.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>This project is still very much a work in progress and any comments or suggestions of potential research avenues will be greatly appreciated. We hope that all the managers considering their options throughout this season have found some useful information to help them climb up the leader board with our analysis of the different chips. It&amp;rsquo;s worth noting that while these strategies performed well during last season for the top 10k, this is not to say the same will happen this season. Of course, you’re free to trust them blindly, which may result in an improvement of your own overall rank.&lt;/p>
&lt;p>&lt;em>This article originally appeared on RTE Brainstorm, the article can be found &lt;a href="https://www.rte.ie/brainstorm/2019/0911/1075310-the-maths-of-fantasy-football/">here&lt;/a>.&lt;/em>&lt;/p></description></item><item><title>Spreading of memes on multiplex networks</title><link>https://obrienjoey.github.io/talk/iccss_amsterdam19/iccss_amsterdam19/</link><pubDate>Thu, 18 Jul 2019 00:00:00 +0000</pubDate><guid>https://obrienjoey.github.io/talk/iccss_amsterdam19/iccss_amsterdam19/</guid><description/></item><item><title>Spreading of Memes on Multiplex Networks</title><link>https://obrienjoey.github.io/publication/memes/</link><pubDate>Thu, 28 Feb 2019 00:00:00 +0000</pubDate><guid>https://obrienjoey.github.io/publication/memes/</guid><description/></item></channel></rss>