Skip to content

Instantly share code, notes, and snippets.

@lmmx
Last active October 22, 2022 10:03
Embed
What would you like to do?
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
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)
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)
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")
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