This commit is contained in:
@@ -1,28 +1,30 @@
|
|||||||
Date,Absolute Position,Relative Position,Number of Players,Number of Teams,Points on Scattergories,Ciaran,Jay,Sam,Drew,Theo,Tom,Ellora,Chloe,Jamie,Christine,Mide,Ellie
|
Date,Absolute Position,Number of Players,Number of Teams,Points on Scattergories,Ciaran,Jay,Sam,Drew,Theo,Tom,Ellora,Chloe,Jamie,Christine,Mide,Ellie,Elliot,Kira
|
||||||
17/03/2025,9,0.692,2,13,10,1,1,0,0,0,0,0,0,0,0,0,0
|
17/03/2025,9,2,13,10,1,1,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
24/03/2025,14,0.933,2,15,4,1,1,0,0,0,0,0,0,0,0,0,0
|
24/03/2025,14,2,15,4,1,1,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
31/03/2025,8,0.444,4,18,7,1,1,1,1,0,0,0,0,0,0,0,0
|
31/03/2025,8,4,18,7,1,1,1,1,0,0,0,0,0,0,0,0,0,0
|
||||||
07/04/2025,9,0.563,2,16,6,1,1,0,0,0,0,0,0,0,0,0,0
|
07/04/2025,9,2,16,6,1,1,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
28/04/2025,9,0.529,3,17,5,1,1,1,0,0,0,0,0,0,0,0,0
|
28/04/2025,9,3,17,5,1,1,1,0,0,0,0,0,0,0,0,0,0,0
|
||||||
26/05/2025,6,0.5,2,12,8,0,1,0,0,1,0,0,0,0,0,0,0
|
26/05/2025,6,2,12,8,0,1,0,0,1,0,0,0,0,0,0,0,0,0
|
||||||
02/06/2025,12,0.857,3,14,4,1,1,1,0,0,0,0,0,0,0,0,0
|
02/06/2025,12,3,14,4,1,1,1,0,0,0,0,0,0,0,0,0,0,0
|
||||||
16/06/2025,6,0.545,3,11,6,1,1,1,0,0,0,0,0,0,0,0,0
|
16/06/2025,6,3,11,6,1,1,1,0,0,0,0,0,0,0,0,0,0,0
|
||||||
30/06/2025,5,0.833,4,6,8,1,1,1,0,0,1,0,0,0,0,0,0
|
30/06/2025,5,4,6,8,1,1,1,0,0,1,0,0,0,0,0,0,0,0
|
||||||
14/07/2025,5,0.625,3,8,8,1,1,0,0,0,0,1,0,0,0,0,0
|
14/07/2025,5,3,8,8,1,1,0,0,0,0,1,0,0,0,0,0,0,0
|
||||||
21/07/2025,4,0.5,2,8,9,1,1,0,0,0,0,0,0,0,0,0,0
|
21/07/2025,4,2,8,9,1,1,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
28/07/2025,6,0.375,4,16,4,1,1,1,0,0,0,1,0,0,0,0,0
|
28/07/2025,6,4,16,4,1,1,1,0,0,0,1,0,0,0,0,0,0,0
|
||||||
04/08/2025,4,0.4,3,10,11,1,1,0,0,0,0,1,0,0,0,0,0
|
04/08/2025,4,3,10,11,1,1,0,0,0,0,1,0,0,0,0,0,0,0
|
||||||
15/09/2025,4,0.333,4,12,5,1,1,1,0,0,0,1,0,0,0,0,0
|
15/09/2025,4,4,12,5,1,1,1,0,0,0,1,0,0,0,0,0,0,0
|
||||||
22/09/2025,5,0.417,3,12,5,1,0,1,0,0,1,0,0,0,0,0,0
|
22/09/2025,5,3,12,5,1,0,1,0,0,1,0,0,0,0,0,0,0,0
|
||||||
29/09/2025,8,0.727,2,11,5,0,1,0,1,0,0,0,0,0,0,0,0
|
29/09/2025,8,2,11,5,0,1,0,1,0,0,0,0,0,0,0,0,0,0
|
||||||
24/11/2025,8,0.889,3,9,6,1,1,0,0,0,0,1,0,0,0,0,0
|
24/11/2025,8,3,9,6,1,1,0,0,0,0,1,0,0,0,0,0,0,0
|
||||||
05/01/2026,4,0.5,4,8,6,1,1,0,0,0,0,0,1,1,0,0,0
|
05/01/2026,4,4,8,6,1,1,0,0,0,0,0,1,1,0,0,0,0,0
|
||||||
26/01/2026,7,0.583,2,12,10,1,1,0,0,0,0,0,0,0,0,0,0
|
26/01/2026,7,2,12,10,1,1,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
02/02/2026,7,0.7,2,10,5,1,1,0,0,0,0,0,0,0,0,0,0
|
02/02/2026,7,2,10,5,1,1,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
16/02/2026,9,0.5,3,18,6,0,1,0,1,0,0,0,0,0,1,0,0
|
16/02/2026,9,3,18,6,0,1,0,1,0,0,0,0,0,1,0,0,0,0
|
||||||
23/02/2026,6,0.6,2,10,10,1,1,0,0,0,0,0,0,0,0,0,0
|
23/02/2026,6,2,10,10,1,1,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
09/03/2026,6,1,5,6,10,1,1,1,0,0,0,0,1,0,0,1,0
|
09/03/2026,6,5,6,10,1,1,1,0,0,0,0,1,0,0,1,0,0,0
|
||||||
30/03/2026,2,0.133,4,15,12,1,1,1,1,0,0,0,0,0,0,0,0
|
30/03/2026,2,4,15,12,1,1,1,1,0,0,0,0,0,0,0,0,0,0
|
||||||
27/04/2026,2,0.222,2,9,7,1,1,0,0,0,0,0,0,0,0,0,0
|
27/04/2026,2,2,9,7,1,1,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
11/05/2026,7,0.777,4,9,6,1,1,0,0,0,0,0,1,0,0,0,1
|
11/05/2026,7,4,9,6,1,1,0,0,0,0,0,1,0,0,0,1,0,0
|
||||||
18/05/2026,8,0.666,3,12,6,1,1,1,0,0,0,0,0,0,0,0,0
|
18/05/2026,8,3,12,6,1,1,1,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
08/06/2026,8,5,11,4,1,1,1,0,0,0,0,0,0,0,0,0,1,1
|
||||||
|
27/07/2026,12,3,13,5,1,1,1,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
|||||||
|
+6
-3
@@ -22,6 +22,9 @@ app = Flask(__name__)
|
|||||||
def get_data_frame(filename):
|
def get_data_frame(filename):
|
||||||
df = pd.read_csv(filename)
|
df = pd.read_csv(filename)
|
||||||
df["Date"] = pd.to_datetime(df["Date"], dayfirst=True)
|
df["Date"] = pd.to_datetime(df["Date"], dayfirst=True)
|
||||||
|
# Relative Position is derived, not entered manually, so it can never
|
||||||
|
# drift out of sync with Absolute Position / Number of Teams.
|
||||||
|
df["Relative Position"] = df["Absolute Position"] / df["Number of Teams"]
|
||||||
df = df.sort_values("Date").reset_index(drop=True)
|
df = df.sort_values("Date").reset_index(drop=True)
|
||||||
return df
|
return df
|
||||||
|
|
||||||
@@ -50,7 +53,7 @@ def generate_position_trend(df):
|
|||||||
df["Relative Percentile"] = df["Relative Position"] * 100
|
df["Relative Percentile"] = df["Relative Position"] * 100
|
||||||
df["Rolling Avg (5)"] = df["Relative Percentile"].rolling(
|
df["Rolling Avg (5)"] = df["Relative Percentile"].rolling(
|
||||||
5, min_periods=1).mean()
|
5, min_periods=1).mean()
|
||||||
df["Attendees"] = build_hovertext(df, constants.PLAYER_NAME_COLUMNS)
|
df["Attendees"] = build_hovertext(df, constants.get_player_columns(df))
|
||||||
|
|
||||||
X = sm.add_constant(df["Date_ordinal"])
|
X = sm.add_constant(df["Date_ordinal"])
|
||||||
model = sm.OLS(df["Relative Percentile"], X).fit()
|
model = sm.OLS(df["Relative Percentile"], X).fit()
|
||||||
@@ -146,7 +149,7 @@ def generate_player_impact(df):
|
|||||||
overall_percentile = df["Relative Position"].mean() * 100
|
overall_percentile = df["Relative Position"].mean() * 100
|
||||||
|
|
||||||
rows = []
|
rows = []
|
||||||
for name in constants.PLAYER_NAME_COLUMNS:
|
for name in constants.get_player_columns(df):
|
||||||
if name not in df.columns:
|
if name not in df.columns:
|
||||||
continue
|
continue
|
||||||
attended = df[df[name] == 1]
|
attended = df[df[name] == 1]
|
||||||
@@ -245,7 +248,7 @@ def generate_scattergories_chart(df):
|
|||||||
|
|
||||||
def generate_player_participation(df):
|
def generate_player_participation(df):
|
||||||
"""Heatmap of which player attended which game."""
|
"""Heatmap of which player attended which game."""
|
||||||
player_cols = [c for c in constants.PLAYER_NAME_COLUMNS if c in df.columns]
|
player_cols = [c for c in constants.get_player_columns(df) if c in df.columns]
|
||||||
df_players = df[player_cols]
|
df_players = df[player_cols]
|
||||||
fig = px.imshow(
|
fig = px.imshow(
|
||||||
df_players.T,
|
df_players.T,
|
||||||
|
|||||||
+16
-28
@@ -8,39 +8,27 @@ def ordinal(n):
|
|||||||
return f"{n}{suffix}"
|
return f"{n}{suffix}"
|
||||||
|
|
||||||
|
|
||||||
PLAYER_NAME_COLUMNS = [
|
# Columns that describe the quiz night itself rather than an individual
|
||||||
"Ciaran",
|
# player's attendance. Any dataframe column not in this set is treated as a
|
||||||
"Jay",
|
# player name, so new players are picked up automatically from data.csv
|
||||||
"Sam",
|
# without needing a code change.
|
||||||
"Drew",
|
NON_PLAYER_COLUMNS = {
|
||||||
"Theo",
|
"Date",
|
||||||
"Tom",
|
"Absolute Position",
|
||||||
"Ellora",
|
"Relative Position",
|
||||||
"Chloe",
|
|
||||||
"Jamie",
|
|
||||||
"Christine",
|
|
||||||
"Mide",
|
|
||||||
"Ellie",
|
|
||||||
]
|
|
||||||
|
|
||||||
FEATURE_COLUMNS = {
|
|
||||||
"Number of Players",
|
"Number of Players",
|
||||||
"Number of Teams",
|
"Number of Teams",
|
||||||
"Points on Scattergories",
|
"Points on Scattergories",
|
||||||
"Ciaran",
|
|
||||||
"Jay",
|
|
||||||
"Sam",
|
|
||||||
"Drew",
|
|
||||||
"Theo",
|
|
||||||
"Tom",
|
|
||||||
"Ellora",
|
|
||||||
"Chloe",
|
|
||||||
"Jamie",
|
|
||||||
"Christine",
|
|
||||||
"Mide",
|
|
||||||
"Ellie",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_player_columns(df):
|
||||||
|
"""Return the list of player-name columns present in df, in CSV order."""
|
||||||
|
return [c for c in df.columns if c not in NON_PLAYER_COLUMNS]
|
||||||
|
|
||||||
|
|
||||||
|
FEATURE_COLUMNS = NON_PLAYER_COLUMNS - {"Date", "Relative Position"}
|
||||||
|
|
||||||
ATTENDANCE_COLORSCHEME = [
|
ATTENDANCE_COLORSCHEME = [
|
||||||
[0, "#E4ECF6"], [1, "#1e3a8a"]
|
[0, "#E4ECF6"], [1, "#1e3a8a"]
|
||||||
]
|
]
|
||||||
|
|||||||
+3
-2
@@ -1,11 +1,12 @@
|
|||||||
from constants import PLAYER_NAME_COLUMNS, ordinal
|
import constants
|
||||||
|
from constants import ordinal
|
||||||
|
|
||||||
|
|
||||||
def generate_player_table(df):
|
def generate_player_table(df):
|
||||||
header = [["Player", "Appearances", "Avg. Relative Percentile", "Spent"]]
|
header = [["Player", "Appearances", "Avg. Relative Percentile", "Spent"]]
|
||||||
|
|
||||||
player_stats = []
|
player_stats = []
|
||||||
for name in PLAYER_NAME_COLUMNS:
|
for name in constants.get_player_columns(df):
|
||||||
if name in df.columns:
|
if name in df.columns:
|
||||||
attended = df[df[name] == 1]
|
attended = df[df[name] == 1]
|
||||||
n = len(attended)
|
n = len(attended)
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@ def _max_team_streak(dates):
|
|||||||
|
|
||||||
|
|
||||||
def _max_player_streak(df):
|
def _max_player_streak(df):
|
||||||
names = [col for col in df.columns if col in set(constants.PLAYER_NAME_COLUMNS)]
|
names = constants.get_player_columns(df)
|
||||||
max_streak, max_name = 1, names[0]
|
max_streak, max_name = 1, names[0]
|
||||||
for name in names:
|
for name in names:
|
||||||
local_max = current = 0
|
local_max = current = 0
|
||||||
|
|||||||
Reference in New Issue
Block a user