From 90e40599207ed71923a02edb59378bb68a962880 Mon Sep 17 00:00:00 2001 From: Xuanyi Wang Date: Wed, 12 Apr 2023 01:00:38 +0100 Subject: [PATCH 1/7] Add an example script to visualise driver standings and points (#325) --- .gitignore | 1 + examples/plot_results_tracker.py | 77 ++++++++++++++++++++++++++++++++ examples/results_tracker.html | 14 ++++++ 3 files changed, 92 insertions(+) create mode 100644 examples/plot_results_tracker.py create mode 100644 examples/results_tracker.html diff --git a/.gitignore b/.gitignore index 8b8817988..56166fd96 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ docs/examples_gallery/ # all variations of cache directories *_cache/ +.DS_Store # other data directories reference/ diff --git a/examples/plot_results_tracker.py b/examples/plot_results_tracker.py new file mode 100644 index 000000000..fd7206677 --- /dev/null +++ b/examples/plot_results_tracker.py @@ -0,0 +1,77 @@ +"""Plot GP results in a heatmap +====================================== + +Plot the points for each driven in each race of a given season in a heatmap, as +https://public.tableau.com/app/profile/mateusz.karmalski/viz/F1ResultsTracker2022 +""" + +import time + +import pandas as pd +import plotly.express as px +import plotly.io as pio + +from fastf1.ergast import Ergast + + +############################################################################## +# First, we load the results for season 2022 +ergast = Ergast() +races = ergast.get_race_schedule(2022) # Races in year 2022 +results = [] +for rnd, race in races['raceName'].items(): + temp = ergast.get_race_results(season=2022, round=rnd) # Race results + temp = temp.content[0] # Points scored + sprint = ergast.get_sprint_results(season=2022, round=rnd) + if sprint.content and sprint.description['round'][0] == rnd: # If sprint + temp = pd.merge(temp, sprint.content[0], on='driverCode', how='left') + temp['points'] = temp['points_x'] + temp['points_y'] + temp.drop(columns=['points_x', 'points_y'], inplace=True) + temp['round'] = rnd + 1 # Add race name + temp['race'] = race.removesuffix(' Grand Prix') + temp = temp[['round', 'race', 'driverCode', 'points']] # Only useful cols. + results.append(temp) +results = pd.concat(results) +races = results['race'].drop_duplicates() + +############################################################################## +# Then we ``reshape'' the results to a wide table, where each row represents a +# driver and each column refers to a race, and the cell value is the points +results = results.pivot(index='driverCode', columns='round', values='points') +# Here we have a 22-by-22 matrix (22 races and 22 drivers, incl. DEV and HUL) + +# Order the drivers by their total points +results['total_points'] = results.sum(axis=1) +results = results.sort_values(by='total_points', ascending=False) +results.drop(columns='total_points', inplace=True) + +# Use race name, instead of round no., as column names +results.columns = races + + +############################################################################## +# The final step is to plot a heatmap using plotly +fig = px.imshow( + results, + text_auto=True, + aspect='auto', # Automatically adjust the aspect ratio + color_continuous_scale=[[0, 'rgb(198, 219, 239)'], # Blue scale + [0.25, 'rgb(107, 174, 214)'], + [0.5, 'rgb(33, 113, 181)'], + [0.75, 'rgb(8, 81, 156)'], + [1, 'rgb(8, 48, 107)']], + labels={'x': 'Race', + 'y': 'Driver', + 'color': 'Points'} # Change hover texts +) +fig.update_xaxes(title_text='') # Remove axis titles +fig.update_yaxes(title_text='') +fig.update_yaxes(showgrid=True, gridwidth=1, gridcolor='LightGrey', + showline=True, linewidth=1, linecolor='LightGrey', + tickson='boundaries') +fig.update_xaxes(showgrid=False, showline=False) # Show horizontal grid only +fig.update_layout(plot_bgcolor='rgba(0,0,0,0)') # White background +fig.update_layout(coloraxis_showscale=False) # Remove legend +fig.update_layout(xaxis=dict(side='top')) # x-axis on top +fig.show() +pio.write_html(fig, file='examples/results_tracker.html', auto_open=False) diff --git a/examples/results_tracker.html b/examples/results_tracker.html new file mode 100644 index 000000000..f1ec3a00c --- /dev/null +++ b/examples/results_tracker.html @@ -0,0 +1,14 @@ + + + +
+
+ + \ No newline at end of file From 365ab6581a0989a208e41e5a39d8408435f59ceb Mon Sep 17 00:00:00 2001 From: Xuanyi Wang Date: Wed, 12 Apr 2023 01:06:41 +0100 Subject: [PATCH 2/7] make plotly figure online only to avoid excessively large html file --- examples/plot_results_tracker.py | 4 +++- examples/results_tracker.html | 16 ++-------------- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/examples/plot_results_tracker.py b/examples/plot_results_tracker.py index fd7206677..6972a9c91 100644 --- a/examples/plot_results_tracker.py +++ b/examples/plot_results_tracker.py @@ -74,4 +74,6 @@ fig.update_layout(coloraxis_showscale=False) # Remove legend fig.update_layout(xaxis=dict(side='top')) # x-axis on top fig.show() -pio.write_html(fig, file='examples/results_tracker.html', auto_open=False) +with open('examples/results_tracker.html', 'w') as f: + f.write(fig.to_html(full_html=False, include_plotlyjs='cdn')) +f.close() diff --git a/examples/results_tracker.html b/examples/results_tracker.html index f1ec3a00c..3a7f7976c 100644 --- a/examples/results_tracker.html +++ b/examples/results_tracker.html @@ -1,14 +1,2 @@ - - - -
-
- - \ No newline at end of file +
+
\ No newline at end of file From e02febd4e76039c5016e8bb89810c8976ccfdca3 Mon Sep 17 00:00:00 2001 From: Xuanyi Wang Date: Wed, 12 Apr 2023 01:25:48 +0100 Subject: [PATCH 3/7] fix index error (round starts at one, not zero) --- examples/plot_results_tracker.py | 6 +++--- examples/results_tracker.html | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/plot_results_tracker.py b/examples/plot_results_tracker.py index 6972a9c91..5e16ce18b 100644 --- a/examples/plot_results_tracker.py +++ b/examples/plot_results_tracker.py @@ -20,10 +20,10 @@ races = ergast.get_race_schedule(2022) # Races in year 2022 results = [] for rnd, race in races['raceName'].items(): - temp = ergast.get_race_results(season=2022, round=rnd) # Race results + temp = ergast.get_race_results(season=2022, round=rnd + 1) # Race results temp = temp.content[0] # Points scored - sprint = ergast.get_sprint_results(season=2022, round=rnd) - if sprint.content and sprint.description['round'][0] == rnd: # If sprint + sprint = ergast.get_sprint_results(season=2022, round=rnd + 1) + if sprint.content and sprint.description['round'][0] == rnd + 1: temp = pd.merge(temp, sprint.content[0], on='driverCode', how='left') temp['points'] = temp['points_x'] + temp['points_y'] temp.drop(columns=['points_x', 'points_y'], inplace=True) diff --git a/examples/results_tracker.html b/examples/results_tracker.html index 3a7f7976c..6e5dba704 100644 --- a/examples/results_tracker.html +++ b/examples/results_tracker.html @@ -1,2 +1,2 @@
-
\ No newline at end of file +
\ No newline at end of file From dc1b13f7b0a20fec530ad70951080e9cc6b4175f Mon Sep 17 00:00:00 2001 From: Xuanyi Wang Date: Wed, 12 Apr 2023 23:18:24 +0100 Subject: [PATCH 4/7] use sphinx to render plotly output in example gallery * `docs/conf.py`: set plotly render to be sphinx * `examples/plot_results_tracker.py`: improve comments * `requirements-dev.txt`: add plotly to requirements --- docs/conf.py | 13 +++++--- examples/plot_results_tracker.py | 56 ++++++++++++++++++-------------- examples/results_tracker.html | 2 -- requirements-dev.txt | 3 +- 4 files changed, 43 insertions(+), 31 deletions(-) delete mode 100644 examples/results_tracker.html diff --git a/docs/conf.py b/docs/conf.py index 3ae61365f..23e4914c7 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -4,13 +4,15 @@ # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # -# import os -# import sys -# sys.path.insert(0, os.path.abspath('../')) +import sys import os.path from datetime import datetime import re +import plotly.io as pio + +sys.path.insert(0, os.path.abspath('../')) + # -- Project information ----------------------------------------------------- # load version number from file in sources dir without importing @@ -79,8 +81,11 @@ html_css_files = [ 'css/custom.css', ] - doc_cache = os.path.abspath('../doc_cache') + +# Plotly configuration: use sphinx to render plotly +pio.renderers.default = 'sphinx_gallery' + # matplotlib plot directive options plot_pre_code = f"import numpy as np;" \ f"from matplotlib import pyplot as plt;" \ diff --git a/examples/plot_results_tracker.py b/examples/plot_results_tracker.py index 5e16ce18b..ad750606e 100644 --- a/examples/plot_results_tracker.py +++ b/examples/plot_results_tracker.py @@ -1,46 +1,55 @@ -"""Plot GP results in a heatmap +"""Plot driver standings in a heatmap ====================================== Plot the points for each driven in each race of a given season in a heatmap, as https://public.tableau.com/app/profile/mateusz.karmalski/viz/F1ResultsTracker2022 """ -import time - import pandas as pd import plotly.express as px -import plotly.io as pio from fastf1.ergast import Ergast ############################################################################## -# First, we load the results for season 2022 +# First, we load the results for season 2022. ergast = Ergast() races = ergast.get_race_schedule(2022) # Races in year 2022 results = [] + +# For each race in the season for rnd, race in races['raceName'].items(): - temp = ergast.get_race_results(season=2022, round=rnd + 1) # Race results - temp = temp.content[0] # Points scored + + # Get results. Note that we use the round no. + 1, because the round no. + # starts from one (1) instead of zero (0) + temp = ergast.get_race_results(season=2022, round=rnd + 1) + temp = temp.content[0] + + # If there is a sprint, get the results as well sprint = ergast.get_sprint_results(season=2022, round=rnd + 1) if sprint.content and sprint.description['round'][0] == rnd + 1: temp = pd.merge(temp, sprint.content[0], on='driverCode', how='left') + # Add sprint points and race points to get the total temp['points'] = temp['points_x'] + temp['points_y'] temp.drop(columns=['points_x', 'points_y'], inplace=True) - temp['round'] = rnd + 1 # Add race name + + # Add round no. and grand prix name + temp['round'] = rnd + 1 temp['race'] = race.removesuffix(' Grand Prix') - temp = temp[['round', 'race', 'driverCode', 'points']] # Only useful cols. + temp = temp[['round', 'race', 'driverCode', 'points']] # Keep useful cols. results.append(temp) + +# Append all races into a single dataframe results = pd.concat(results) races = results['race'].drop_duplicates() ############################################################################## -# Then we ``reshape'' the results to a wide table, where each row represents a -# driver and each column refers to a race, and the cell value is the points +# Then we “reshape” the results to a wide table, where each row represents a +# driver and each column refers to a race, and the cell value is the points. results = results.pivot(index='driverCode', columns='round', values='points') # Here we have a 22-by-22 matrix (22 races and 22 drivers, incl. DEV and HUL) -# Order the drivers by their total points +# Rank the drivers by their total points results['total_points'] = results.sum(axis=1) results = results.sort_values(by='total_points', ascending=False) results.drop(columns='total_points', inplace=True) @@ -62,18 +71,17 @@ [1, 'rgb(8, 48, 107)']], labels={'x': 'Race', 'y': 'Driver', - 'color': 'Points'} # Change hover texts + 'color': 'Points'} # Change hover texts ) -fig.update_xaxes(title_text='') # Remove axis titles +fig.update_xaxes(title_text='') # Remove axis titles fig.update_yaxes(title_text='') +fig.update_yaxes(tickmode='linear') # Show all ticks, i.e. driver names fig.update_yaxes(showgrid=True, gridwidth=1, gridcolor='LightGrey', - showline=True, linewidth=1, linecolor='LightGrey', - tickson='boundaries') -fig.update_xaxes(showgrid=False, showline=False) # Show horizontal grid only -fig.update_layout(plot_bgcolor='rgba(0,0,0,0)') # White background -fig.update_layout(coloraxis_showscale=False) # Remove legend -fig.update_layout(xaxis=dict(side='top')) # x-axis on top -fig.show() -with open('examples/results_tracker.html', 'w') as f: - f.write(fig.to_html(full_html=False, include_plotlyjs='cdn')) -f.close() + showline=False, + tickson='boundaries') # Show horizontal grid only +fig.update_xaxes(showgrid=False, showline=False) # And remove vertical grid +fig.update_layout(plot_bgcolor='rgba(0,0,0,0)') # White background +fig.update_layout(coloraxis_showscale=False) # Remove legend +fig.update_layout(xaxis=dict(side='top')) # x-axis on top +fig.update_layout(margin=dict(l=0, r=0, b=0, t=0)) # Remove border margins +fig diff --git a/examples/results_tracker.html b/examples/results_tracker.html deleted file mode 100644 index 6e5dba704..000000000 --- a/examples/results_tracker.html +++ /dev/null @@ -1,2 +0,0 @@ -
-
\ No newline at end of file diff --git a/requirements-dev.txt b/requirements-dev.txt index 1605a6ccc..7ee88cba9 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -12,4 +12,5 @@ readme-renderer xdoctest pre-commit requests>=2.28.1 -websockets>=10.3 \ No newline at end of file +websockets>=10.3 +plotly \ No newline at end of file From 22ec642da286ae7fa2645f474fa47d21f0d593a2 Mon Sep 17 00:00:00 2001 From: Xuanyi Wang Date: Wed, 12 Apr 2023 23:28:19 +0100 Subject: [PATCH 5/7] doc path clean up --- docs/conf.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 23e4914c7..77ebd46df 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -4,7 +4,9 @@ # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # -import sys +# import os +# import sys +# sys.path.insert(0, os.path.abspath('../')) import os.path from datetime import datetime import re From 010d37b35b429a4222e3e028766475bb10d2268a Mon Sep 17 00:00:00 2001 From: Xuanyi Wang <31792725+harningle@users.noreply.github.com> Date: Wed, 12 Apr 2023 23:46:25 +0100 Subject: [PATCH 6/7] clean up sys.path forget to remove sys.path before --- docs/conf.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 77ebd46df..f5bfc0fac 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -13,8 +13,6 @@ import plotly.io as pio -sys.path.insert(0, os.path.abspath('../')) - # -- Project information ----------------------------------------------------- # load version number from file in sources dir without importing From 173ba07f98e6e98b0cc09c6d657d95ab24b56861 Mon Sep 17 00:00:00 2001 From: Xuanyi WANG Date: Fri, 14 Apr 2023 20:30:59 +0100 Subject: [PATCH 7/7] fix thumbnail missing for plotly outputs See https://github.com/smarie/mkdocs-gallery/issues/49#issue-1228645281 --- docs/conf.py | 4 +++- examples/plot_results_tracker.py | 2 ++ requirements-dev.txt | 3 ++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index f5bfc0fac..cb67e29b6 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -12,6 +12,7 @@ import re import plotly.io as pio +from plotly.io._sg_scraper import plotly_sg_scraper # -- Project information ----------------------------------------------------- @@ -84,7 +85,7 @@ doc_cache = os.path.abspath('../doc_cache') # Plotly configuration: use sphinx to render plotly -pio.renderers.default = 'sphinx_gallery' +pio.renderers.default = 'sphinx_gallery_png' # matplotlib plot directive options plot_pre_code = f"import numpy as np;" \ @@ -111,6 +112,7 @@ 'gallery_dirs': 'examples_gallery', 'download_all_examples': False, 'remove_config_comments': True, + 'image_scrapers': ('matplotlib', plotly_sg_scraper) # For plotly thumbnail } # options for latexpdf build diff --git a/examples/plot_results_tracker.py b/examples/plot_results_tracker.py index ad750606e..04230e040 100644 --- a/examples/plot_results_tracker.py +++ b/examples/plot_results_tracker.py @@ -7,6 +7,7 @@ import pandas as pd import plotly.express as px +from plotly.io import show from fastf1.ergast import Ergast @@ -85,3 +86,4 @@ fig.update_layout(xaxis=dict(side='top')) # x-axis on top fig.update_layout(margin=dict(l=0, r=0, b=0, t=0)) # Remove border margins fig +show(fig) diff --git a/requirements-dev.txt b/requirements-dev.txt index 7ee88cba9..851428b61 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -13,4 +13,5 @@ xdoctest pre-commit requests>=2.28.1 websockets>=10.3 -plotly \ No newline at end of file +plotly +kaleido \ No newline at end of file