Last active
October 22, 2022 10:03
-
-
Save lmmx/4e5ca0017adc537ff807fe0821340ed6 to your computer and use it in GitHub Desktop.
PMs of GB via https://en.wikipedia.org/wiki/List_of_prime_ministers_of_the_United_Kingdom_by_length_of_tenure (inspired by https://twitter.com/Jonathan_Pryor/status/1583196889435865088)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Shortest | Rank | Prime Minister | Tenure | Terms | Party | Start | Reason | Days | Year | |
---|---|---|---|---|---|---|---|---|---|---|
1 | 56 | Liz Truss | 46 days | 1 | Conservative | 2022 | Resigned | 46 | 2022-01-01 | |
2 | 55 | George Canning | 119 days | 1 | Tory | 1827 | Died | 119 | 1827-01-01 | |
3 | 54 | The Viscount Goderich | 144 days | 1 | Tory | 1827 | Replaced | 144 | 1827-01-01 | |
4 | 53 | Bonar Law | 211 days | 1 | Conservative | 1922 | Resigned due to illness | 211 | 1922-01-01 | |
5 | 52 | The Duke of Devonshire | 225 days | 1 | Whig | 1756 | Replaced | 225 | 1756-01-01 | |
6 | 51 | The Earl of Shelburne | 266 days | 1 | Whig | 1782 | Replaced | 266 | 1782-01-01 | |
7 | 50 | The Earl of Bute | 317 days | 1 | Tory | 1762 | Resigned | 317 | 1762-01-01 | |
8 | 49 | Sir Alec Douglas-Home | 363 days | 1 | Conservative | 1963 | Election | 363 | 1963-01-01 | |
9 | 48 | The Lord Grenville | 1 year, 42 days | 1 | Whig | 1806 | Replaced | 407 | 1806-01-01 | |
10 | 47 | The Duke of Grafton | 1 year, 106 days | 1 | Whig | 1768 | Resigned | 471 | 1768-01-01 | |
11 | 46 | The Earl of Rosebery | 1 year, 109 days | 1 | Liberal | 1894 | Election | 474 | 1894-01-01 | |
12 | 45 | The Marquess of Rockingham | 1 year, 113 days | 2 | Whig | 1765 | Died | 478 | 1765-01-01 | |
13 | 44 | The Earl of Wilmington | 1 year, 119 days | 1 | Whig | 1742 | Died | 484 | 1742-01-01 | |
14 | 43 | Sir Anthony Eden | 1 year, 279 days | 1 | Conservative | 1955 | Resigned due to illness | 644 | 1955-01-01 | |
15 | 42 | The Earl of Aberdeen | 2 years, 42 days | 1 | Peelite | 1852 | Resigned | 772 | 1852-01-01 | |
16 | 41 | The Earl of Chatham | 2 years, 76 days | 1 | Whig | 1766 | Resigned due to illness | 806 | 1766-01-01 | |
17 | 40 | George Grenville | 2 years, 85 days | 1 | Whig | 1763 | Replaced | 815 | 1763-01-01 | |
18 | 39 | Sir Henry Campbell-Bannerman | 2 years, 122 days | 1 | Liberal | 1905 | Resigned due to illness | 852 | 1905-01-01 | |
19 | 38 | Spencer Perceval | 2 years, 221 days | 1 | Tory | 1809 | Assassinated | 951 | 1809-01-01 | |
20 | 37 | Gordon Brown | 2 years, 318 days | 1 | Labour | 2007 | Election | 1048 | 2007-01-01 | |
21 | 36 | The Duke of Wellington | 2 years, 320 days | 2 | Tory | 1828 | Replaced | 1050 | 1828-01-01 | |
22 | 35 | Neville Chamberlain | 2 years, 348 days | 1 | Conservative | 1937 | Resigned | 1078 | 1937-01-01 | |
23 | 34 | Theresa May | 3 years, 11 days | 1 | Conservative | 2016 | Resigned | 1106 | 2016-01-01 | |
24 | 33 | James Callaghan | 3 years, 29 days | 1 | Labour | 1976 | Election | 1124 | 1976-01-01 | |
25 | 32 | Boris Johnson | 3 years, 44 days | 1 | Conservative | 2019 | Resigned | 1139 | 2019-01-01 | |
26 | 31 | Henry Addington | 3 years, 54 days | 1 | Tory | 1801 | Replaced | 1149 | 1801-01-01 | |
27 | 30 | The Duke of Portland | 3 years, 82 days | 2 | Whig / Tory | 1783 | Resigned due to illness | 1177 | 1783-01-01 | |
28 | 29 | Arthur Balfour | 3 years, 145 days | 1 | Conservative | 1902 | Resigned | 1240 | 1902-01-01 | |
29 | 28 | The Earl Grey | 3 years, 229 days | 1 | Whig | 1830 | Resigned | 1324 | 1830-01-01 | |
30 | 27 | Edward Heath | 3 years, 259 days | 1 | Conservative | 1970 | Election | 1354 | 1970-01-01 | |
31 | 26 | The Earl of Derby | 3 years, 280 days | 3 | Conservative | 1852 | Resigned due to illness | 1375 | 1852-01-01 | |
32 | 25 | Sir Robert Peel | 5 years, 57 days | 2 | Conservative | 1834 | Resigned | 1883 | 1834-01-01 | |
33 | 24 | David Lloyd George | 5 years, 317 days | 1 | Liberal | 1916 | Resigned | 2143 | 1916-01-01 | |
34 | 23 | David Cameron | 6 years, 63 days | 1 | Conservative | 2010 | Resigned | 2254 | 2010-01-01 | |
35 | 22 | Clement Attlee | 6 years, 92 days | 1 | Labour | 1945 | Election | 2283 | 1945-01-01 | |
36 | 21 | Lord John Russell | 6 years, 110 days | 2 | Whig / Liberal | 1846 | Election | 2301 | 1846-01-01 | |
37 | 20 | John Major | 6 years, 155 days | 1 | Conservative | 1990 | Election | 2346 | 1990-01-01 | |
38 | 19 | The Viscount Melbourne | 6 years, 255 days | 2 | Whig | 1834 | Resigned | 2446 | 1834-01-01 | |
39 | 18 | Harold Macmillan | 6 years, 281 days | 1 | Conservative | 1957 | Resigned | 2472 | 1957-01-01 | |
40 | 17 | Ramsay MacDonald | 6 years, 289 days | 2 | Labour / National Labour | 1924 | Resigned due to illness | 2480 | 1924-01-01 | |
41 | 16 | Benjamin Disraeli | 6 years, 339 days | 2 | Conservative | 1868 | Election | 2530 | 1868-01-01 | |
42 | 15 | Stanley Baldwin | 7 years, 82 days | 3 | Conservative | 1923 | Resigned | 2638 | 1923-01-01 | |
43 | 14 | The Duke of Newcastle | 7 years, 205 days | 2 | Whig | 1754 | Replaced | 2761 | 1754-01-01 | |
44 | 13 | Harold Wilson | 7 years, 279 days | 2 | Labour | 1964 | Resigned | 2835 | 1964-01-01 | |
45 | 12 | Sir Winston Churchill | 8 years, 239 days | 2 | Conservative | 1940 | Resigned due to illness | 3161 | 1940-01-01 | |
46 | 11 | H. H. Asquith | 8 years, 244 days | 1 | Liberal | 1908 | Resigned | 3166 | 1908-01-01 | |
47 | 10 | The Viscount Palmerston | 9 years, 141 days | 2 | Whig / Liberal | 1855 | Died | 3428 | 1855-01-01 | |
48 | 9 | Tony Blair | 10 years, 56 days | 1 | Labour | 1997 | Resigned | 3708 | 1997-01-01 | |
49 | 8 | Henry Pelham | 10 years, 191 days | 1 | Whig | 1743 | Died | 3843 | 1743-01-01 | |
50 | 7 | Margaret Thatcher | 11 years, 208 days | 1 | Conservative | 1979 | Resigned | 4225 | 1979-01-01 | |
51 | 6 | Lord North | 12 years, 58 days | 1 | Tory | 1770 | Resigned | 4441 | 1770-01-01 | |
52 | 5 | William Ewart Gladstone | 12 years, 126 days | 4 | Liberal | 1868 | Resigned | 4509 | 1868-01-01 | |
53 | 4 | The Marquess of Salisbury | 13 years, 252 days | 3 | Conservative | 1885 | Resigned due to illness | 5000 | 1885-01-01 | |
54 | 3 | The Earl of Liverpool | 14 years, 305 days | 1 | Tory | 1812 | Resigned due to illness | 5418 | 1812-01-01 | |
55 | 2 | William Pitt the Younger | 18 years, 343 days | 2 | Tory | 1783 | Died | 6917 | 1783-01-01 | |
56 | 1 | Sir Robert Walpole | 20 years, 314 days | 1 | Whig | 1721 | Resigned | 7619 | 1721-01-01 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import pandas as pd | |
__all__ = [ | |
"count_total_days", | |
"url", | |
"preprocess_table", | |
"df", | |
] | |
url = "https://en.wikipedia.org/wiki/List_of_prime_ministers_of_the_United_Kingdom_by_length_of_tenure" | |
def count_total_days(tenure: str) -> int: | |
years, days = [int(n) for n in ["0", *tenure.split()] if n.isnumeric()][-2:] | |
years_in_days = int(365.25 * years) | |
return years_in_days + days | |
def preprocess_table(url) -> pd.DataFrame: | |
df = ( | |
pd.read_html(url, match="Tenure") | |
.pop() | |
.drop(columns="Ref.") | |
.rename({"Rank": "Longest"}) | |
.apply(lambda src: src.replace(r"\s\(.*", "", regex=True)) | |
) | |
df.columns = ( | |
pd.Series(df.columns) | |
.str.encode("ascii", "ignore") | |
.str.decode("ascii") | |
.str.replace(r"[\[\.].*", "", regex=True) | |
.str.replace("for exit", "", regex=False) | |
.str.replace("length", "", regex=False) | |
.str.strip(" ") | |
) | |
df.Reason = df.Reason.astype("category") | |
df["Days"] = df.Tenure.apply(count_total_days) | |
df["Year"] = pd.to_datetime(df.Start, format="%Y") | |
shortest = df.Rank.to_list()[::-1] | |
df = ( | |
pd.concat((pd.Series(shortest, name="Shortest"), df), axis=1) | |
.sort_values(by="Shortest") | |
.reset_index(drop=True) | |
) | |
return df | |
df = preprocess_table(url) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import matplotlib.pyplot as plt | |
import pandas as pd | |
from tenure_data_preproc import df | |
__all__ = [ | |
"fig_scale", | |
"colour_by_party", | |
"get_max_days_that_year", | |
"get_min_days_that_year", | |
"getcol", | |
"round_up", | |
] | |
fig_scale = 2 | |
plt.rcParams.update( | |
{ | |
"text.usetex": True, | |
"font.family": "Helvetica", | |
"font.size": 18 * (1.3 * fig_scale), | |
} | |
) | |
def colour_by_party(vals): | |
a, b = vals | |
if b < 0: | |
colour = "w" | |
else: | |
year = a.year | |
days = int(b) | |
matched = df.query("Start == @year & Days == @days").squeeze() | |
assert not matched.empty, "No result!" | |
if "Labour" in matched.Party: | |
colour = "red" | |
elif "Conservative" in matched.Party: | |
colour = "blue" | |
elif "Liberal" in matched.Party: | |
colour = "orange" | |
else: | |
colour = "black" | |
return colour | |
def getcol(values): | |
return values.reset_index().apply(colour_by_party, axis=1) | |
def get_max_days_that_year(t, zero=float("-inf")): | |
days = df["Days"][df.Start == t.year] | |
return zero if days.empty else days.max() | |
def get_min_days_that_year(t, zero=float("-inf")): | |
days = df["Days"][df.Start == t.year] | |
return zero if days.empty else days.min() | |
def round_up(val, nearest=1000): | |
return int(nearest * ((val // nearest) + 1)) | |
# def getcol(values, default): | |
# colours = [default if val > 0 else "w" for val in values] | |
# return pd.Categorical(colours) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from pathlib import Path | |
import matplotlib.pyplot as plt | |
import numpy as np | |
import pandas as pd | |
from numpy import linspace | |
from tenure_plot_utils import ( | |
fig_scale, | |
get_max_days_that_year, | |
get_min_days_that_year, | |
getcol, | |
round_up, | |
) | |
__all__ = ["plot_pms"] | |
def plot_pms(df) -> None: | |
times = pd.date_range( | |
df.Year.min(), end=df.Year.max() + pd.to_timedelta(366, unit="d"), freq="1y" | |
) | |
fig, ax = plt.subplots(1) | |
fig_w, fig_h = (15.5, 10.5) | |
fig.set_size_inches(fig_w * fig_scale, fig_h * fig_scale) | |
pad = int(15 * fig_scale * 1.5) | |
title = "$UK$ $Prime$ $Ministerial$ $tenure$" | |
ax.set_title(title, pad=pad) | |
plt.ylim(ymin=0, ymax=round_up(df.Days.max())) | |
# url = "https://emojipedia-us.s3.dualstack.us-west-1.amazonaws.com/thumbs/120/whatsapp/326/leafy-green_1f96c.png" | |
filename = "leafy-green_1f96c.png" | |
lettuce_p = Path(__file__).parent / filename | |
opacity = 1.0 # [1.0, 1.0, 1.0, 0.1] # | |
lettuce_img = plt.imread(lettuce_p) * opacity | |
ax.set_xlabel("Date entered office", labelpad=pad) | |
ax.set_ylabel("Tenure /days", labelpad=pad) | |
fig.autofmt_xdate() | |
ax.minorticks_on() | |
ax.tick_params("both", length=20, width=2, which="major") | |
ax.tick_params("x", length=10, width=1, which="minor") | |
min_values = times.to_series().apply(get_min_days_that_year) | |
max_values = times.to_series().apply(get_max_days_that_year) | |
# Most has been 2 per year, so max and min will cover both | |
days_agg = df.groupby("Start")["Days"].agg(["min", "max"]) | |
df.merge(days_agg, on="Start").rename({"min": "MinTenure", "max": "MaxTenure"}) | |
markersize = 50 * fig_scale**2.5 | |
plt.scatter(times, min_values, c=getcol(min_values), s=markersize) # alpha=0.5, | |
plt.scatter(times, max_values, c=getcol(max_values), s=markersize) # alpha=0.5, | |
x = linspace(df.Start.min(), 2000, num=4) / 2 | |
y = np.ones_like(x) * 1000 | |
ax_width = ax.get_window_extent().width + 500 | |
fig_width = fig.get_window_extent().width | |
fig_height = fig.get_window_extent().height | |
lettuce_size = ax_width / (fig_width * 40) | |
lettuce_axs = [None for i in range(len(x))] | |
for i in range(len(x)): | |
loc = ax.transData.transform((x[i], y[i])) | |
old_x, old_y = ( | |
loc[0] / fig_width - lettuce_size / 2, | |
loc[1] / fig_height - lettuce_size / 2, | |
) | |
x_proportion = 0.815 | |
y_proportion = 0.21 | |
lvals = [ | |
x_proportion, | |
y_proportion, | |
lettuce_size, | |
lettuce_size, | |
] | |
lettuce_axs[i] = fig.add_axes( | |
lvals, | |
anchor="C", | |
) | |
lettuce_axs[i].imshow(lettuce_img) | |
lettuce_axs[i].axis("off") | |
xdata, ydata = 0, 0 | |
xdisplay, ydisplay = ax.transData.transform((xdata, ydata)) | |
annotate = True | |
if annotate: | |
ax.annotate( | |
"45\ndays", | |
xy=(0.95, 0.008), | |
xycoords="axes fraction", | |
fontsize=int(10 * (1.4 * fig_scale)), | |
xytext=(-(int(50 * fig_scale)), int(26 * fig_scale)), | |
textcoords="offset points", | |
bbox=dict(boxstyle="round", ec="white", fc="white", alpha=0.0), | |
arrowprops=dict( | |
arrowstyle="-", | |
connectionstyle="angle,angleA=120,angleB=0,rad=10", | |
), | |
) | |
legenditems = [ | |
( | |
plt.Line2D( | |
[], | |
[], | |
color="w", | |
marker="o", | |
markerfacecolor=c, | |
# alpha=0.9, | |
ms=markersize / 10, | |
), | |
party, | |
) | |
for party, c in { | |
"Labour": "red", | |
"Conservative": "blue", | |
"Liberal": "orange", | |
"Other": "black", | |
}.items() | |
] | |
ax.legend(*zip(*legenditems), fontsize="small") | |
show = False | |
if show: | |
plt.show() | |
else: | |
plt.savefig("tenure.png") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from tenure_viz import plot_pms | |
from tenure_data_preproc import df # isort: skip | |
plot_pms(df) | |
# df.to_csv("tenure_data.tsv", index=False, sep="\t") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment