Statistiky
Zdrojová data pro statistiky stahujte zde. Můžete se pak vlastními výstupy z dat pochlubit v reportážích :)
%matplotlib inline
import warnings; warnings.simplefilter('ignore')
"""
++Největší tým
++nejúspornější tým
"""
# %% select backend
import pandas as pd
import numpy as np
import matplotlib
# matplotlib.use('TKAgg', warn=False, force=True)
from matplotlib import pyplot as plt
pd.set_option('display.width', 120)
plt.cm.get_cmap('tab20c')
# %% const
CELKEM_TYMU = 239
# %% functions
def get_actions(this_df: pd.DataFrame, stat='D'):
counts = this_df.groupby(['time', 'success', 'action'])['team_name'].count(). \
unstack().unstack(). \
resample(stat).sum().fillna(0)
return counts
def crop_half(this_df: pd.DataFrame):
this_df = this_df.sort_values(ascending=True)
return this_df[len(this_df) // 2:len(this_df)]
def crop_std(this_df: pd.DataFrame):
this_df = this_df.sort_values(ascending=True)
return this_df[this_df > this_df.std()]
def get_hint_action(action: str):
h = hints_df.set_index('date' + action)
h = h[h.index.notnull()]
h.index = pd.to_datetime(h.index)
h['action'] = action[1:]
h['success'] = True
h['time'] = h.index
return h
# vectorized haversine function
def haversine(lat1, lon1, lat2, lon2, to_radians=True, earth_radius=6371):
"""
slightly modified version: of http://stackoverflow.com/a/29546836/2901002
Calculate the great circle distance between two points
on the earth (specified in decimal degrees or in radians)
All (lat, lon) coordinates must have numeric dtypes and be of equal length.
"""
if to_radians:
lat1, lon1, lat2, lon2 = np.radians([lat1, lon1, lat2, lon2])
a = np.sin((lat2 - lat1) / 2.0) ** 2 + \
np.cos(lat1) * np.cos(lat2) * np.sin((lon2 - lon1) / 2.0) ** 2
return earth_radius * 2 * np.arcsin(np.sqrt(a))
def label_bar(ax, text_format={}, is_inside=True, **kwargs):
"""
Attach a text label to each bar displaying its y value
"""
bars = ax.patches
max_y_value = max(bar.get_height() for bar in bars)
if is_inside:
distance = max_y_value * 0.05
else:
distance = max_y_value * 0.01
for bar in bars:
text = text_format.format(bar.get_height())
text_x = bar.get_x() + bar.get_width() / 2
if is_inside:
text_y = bar.get_height() - distance
else:
text_y = bar.get_height() + distance
ax.text(text_x, text_y, text, ha='center', va='bottom', **kwargs)
def label_barh(ax, text_format={}, is_inside=True, **kwargs):
"""
Attach a text label to each horizontal bar displaying its y value
"""
bars = ax.patches
max_y_value = max(bar.get_height() for bar in bars)
if is_inside:
distance = max_y_value * 0.05
else:
distance = max_y_value * 0.01
for bar in bars:
text = text_format.format(bar.get_width())
if is_inside:
text_x = bar.get_width() - distance
else:
text_x = bar.get_width() + distance
text_y = bar.get_y() + bar.get_height() / 2
ax.text(text_x, text_y, text, va='center', **kwargs)
# %% preprocess main table
df_actions = pd.read_csv('actions.tsv', sep='\t')
df_actions['success'] = df_actions['success'].astype(bool)
df_actions['time'] = pd.to_datetime(df_actions['time_user'].fillna(df_actions['time_server']))
df_actions['stage'] = df_actions['stage_num'].fillna(-1).astype(int).astype(str).str.zfill(2) + ' - ' + df_actions['stage_name'].fillna('')
df_actions = df_actions.set_index(df_actions['time'])
# %% preprocess stages table
df_stage = pd.read_csv('stages.tsv', sep='\t')
df_stage['stage'] = df_stage['num'].fillna(-1).astype(int).astype(str).str.zfill(2) + ' - ' + df_stage['name'].fillna('')
df_stage['distance'] = np.vectorize(haversine)(df_stage.lat.shift(), df_stage.lng.shift(), df_stage.loc[:, 'lat'], df_stage.loc[:, 'lng'])
df_stage.distance = df_stage.distance.fillna(0)
# %% preprocess hint actions
hints_df = pd.read_csv('hints.tsv', sep='\t')
hints_df['stage'] = hints_df['stage_num'].fillna(-1).astype(int).astype(str).str.zfill(2) + ' - ' + hints_df['stage_name'].fillna('')
hint_actions = pd.concat([get_hint_action('_found'), get_hint_action('_used')], sort=True)
hint_actions.index = pd.to_datetime(hint_actions.index)
# %% merge all
df = pd.concat([df_actions, hint_actions], axis=0, sort=True)
df.index = df.index.tz_localize('CET')
NÁPOVĚDY¶
# %% hints
napovedni_kody = hints_df.code.unique().tolist()
ziskane_napovedy = hints_df.groupby('team_name')['code'].count().sort_values()
vyuzite_napovedy = hints_df[hints_df.taken == 1].groupby('team_name')['code'].count().sort_values()
tymy_napovedy = pd.concat([ziskane_napovedy, vyuzite_napovedy],
axis=1, keys=['ziskane', 'vyuzite'], sort=True).fillna(0).astype(int)
vyuziti_tymy = tymy_napovedy.ziskane - tymy_napovedy.vyuzite
ziskali_vsechny = ziskane_napovedy[ziskane_napovedy == len(napovedni_kody)].index.tolist()
napovedy_na_sifry = hints_df[hints_df.taken == 1].groupby('stage')['team_name'].count().sort_values(ascending=True)
# TODO procento z vyrsenych kolik si vzalo napovedu
print('Celkem bylo {} nápovědních kódů:'.format(len(napovedni_kody)), ', '.join(napovedni_kody))
print('\nKódy získalo {} týmů.'.format(len(hints_df.groupby('team_name')['code'].count())))
print('\nNejvíce nevyužitých kódů ({}) vlastní tým: '.format(vyuziti_tymy.max()),
', '.join(vyuziti_tymy[vyuziti_tymy == vyuziti_tymy.max()].index.tolist()))
print('\nVšech {} kódů získaly týmy: '.format(len(napovedni_kody)), ', '.join(ziskali_vsechny))
Speciálně bychom chtěli vyzvednout tým Freezy Bee, který došel do konce hry bez využití jediné nápovědy.
Dále Brouk v hlavě, který k dosažení konce využil pouze jednu nápovědu.
popisky = napovedy_na_sifry.values.tolist()
fig_napovedy = napovedy_na_sifry.plot.barh(figsize=(8,6))
value_format = "{}" # displaying values as number
label_barh(fig_napovedy, text_format=value_format, is_inside=False)
fig_napovedy.set_title('Využítí nápověd na jednotlivé šífry')
fig_napovedy.yaxis.tick_right()
AKTIVITA¶
# %% hourly stats
interesting_actions = df['action'].isin(['used', 'found', 'pickup', 'solve'])
sifra_actions = df['action'].isin(['pickup', 'solve'])
hourly_actions = df[interesting_actions].resample('H')['action'].count()
hourly_stats = hourly_actions.groupby(hourly_actions.index.hour).sum()
hourly_stats.index += 1
hourly_stats.index.name = 'Hodina'
fig_hourly = hourly_stats.plot()
fig_hourly.set_title('Statistika hodinové aktivity v průběhu dne')
plt.tight_layout()
# %% translate
translate = {
'(True, found)': 'Vyzvednutí nápovědy',
'(True, pickup)': 'Vyzvednutí šifry',
'(True, used)': 'Použití nápovědy',
'(True, solve)': 'Vyřešení šifry',
'(False, solve)': 'Neúspěšné řešení šifry',
}
zajimave_akce = [(True, 'pickup'), (False, 'solve'), (True, 'solve'), (True, 'found'), (True, 'used')]
# %% daily stats
actions = get_actions(df[interesting_actions])
actions.index += 1
actions.index.name = 'Dny v týdnu'
actions.columns = actions.columns.swaplevel()
daily_stats = actions[zajimave_akce].groupby(actions.index.weekday).sum().plot.bar(stacked=True)
daily_stats.set_title('Statistika denní aktivity v průběhu týdne | 1-pondělí')
h, labels = daily_stats.get_legend_handles_labels()
daily_stats.legend(labels=[translate[l] for l in labels])
plt.tight_layout()
# %% weely stats
actions_weekly = get_actions(df[interesting_actions], 'W')
actions_weekly.columns = actions_weekly.columns.swaplevel()
fig_weekly = actions_weekly[zajimave_akce[:-2]].plot.area(stacked=True, figsize=(10,6))
fig_weekly.set_title('Statistika týdenní aktivity v průběhu roku')
actions_weekly[[(True, 'found'), (True, 'used')]].plot(stacked=False, ax=fig_weekly)
h, labels = fig_weekly.get_legend_handles_labels()
fig_weekly.legend(labels=[translate[l] for l in labels])
plt.tight_layout()
# %% print days with no activity
all_actions = actions[actions.columns].sum(axis=1)
print('Dny s maximální aktivitou: ',
', '.join(all_actions[all_actions == all_actions.max()].index.strftime('%Y-%m-%d').tolist()))
print('Dny bez aktivity: ',
', '.join(all_actions[all_actions == 0].index.strftime('%Y-%m-%d').tolist()))
HALUZENͶ
# %% haluzeni
GATE = 8
haluz = df_actions.loc[(df_actions.action == 'solve') & (df_actions.success == False)]
haluz_tymy = haluz.groupby(['stage', 'team_name'])['action'].count().unstack().fillna(0)
pod10 = lambda k: k if k < GATE else 0
nad10 = lambda k: k if k >= GATE else 0
haluz_tymy_ostatni = haluz_tymy.apply(lambda k: k.apply(pod10))
haluz_tymy_hlavni = haluz_tymy.apply(lambda k: k.apply(nad10))
sifra_ostatni = haluz_tymy_ostatni.sum()
tym_ostatni = haluz_tymy_ostatni.T.sum()
haluz_tymy_hlavni["Ostatní"] = tym_ostatni
haluz_tymy_hlavni.loc["Ostatní"] = sifra_ostatni
vybrane_tymy = haluz_tymy.sum().sort_values()[-10:]
vybrane_sifry = haluz_tymy.T.sum().sort_values()
sifry_podle_tymu = haluz_tymy_hlavni.reindex(vybrane_sifry.index)
tmp = sifry_podle_tymu.sum()
sifry_podle_tymu = sifry_podle_tymu.reindex(columns=tmp[tmp > 0].index)
sifry_podle_tymu.index.name = 'Šifry'
sifry_podle_tymu.columns.name = 'Týmy'
fig_sifry_podle_tymu = sifry_podle_tymu.plot.barh(stacked=True, figsize=(8,6))
fig_sifry_podle_tymu.set_title("Nejvíc haluzí na šifře")
plt.tight_layout()
tymy_podle_sifer = haluz_tymy_hlavni.T.reindex(vybrane_tymy.index)
tmp = tymy_podle_sifer.sum()
tymy_podle_sifer = tymy_podle_sifer.reindex(columns=tmp[tmp > 0].index)
tymy_podle_sifer.index.name = 'Týmy'
tymy_podle_sifer.columns.name = 'Šifry'
fig_tymy_podle_sifer = tymy_podle_sifer.plot.barh(stacked=True)
fig_tymy_podle_sifer.set_title("Největší haluziči")
plt.tight_layout()
ŠIFRY¶
# %% sifry
sifry_solved = df[(df.success == True) & (df.action == 'solve')].groupby('stage')['action'].count().sort_values()
sifry_solved.loc['Registrace'] = CELKEM_TYMU
sifry_solved.index.name = 'Šifry'
fig_sifry_solved =sifry_solved.sort_values().plot.barh(figsize=(10,8))
fig_sifry_solved.set_title("Počet týmů, co vyřešili danou šifru")
label_barh(fig_sifry_solved, text_format=value_format, is_inside=False)
plt.tight_layout()
# napovedni_kody = hints_df.code.unique().tolist()
# ziskane_napovedy = hints_df.groupby('team_name')['code'].count().sort_values()
# vyuzite_napovedy = hints_df[hints_df.taken == 1].groupby('team_name')['code'].count().sort_values()
# tymy_napovedy = pd.concat([ziskane_napovedy, vyuzite_napovedy],
# axis=1, keys=['ziskane', 'vyuzite'], sort=True).fillna(0).astype(int)
# vyuziti_tymy = tymy_napovedy.ziskane - tymy_napovedy.vyuzite
# ziskali_vsechny = ziskane_napovedy[ziskane_napovedy == len(napovedni_kody)].index.tolist()
# napovedy_na_sifry = hints_df[hints_df.taken == 1].groupby('stage')['team_name'].count().sort_values(ascending=True)
actions_by_team_stage = df[df.success & (df.stage != "-1 - ")][['team_name','stage','action']].set_index(['team_name', 'stage'])
tymy_vyresily = actions_by_team_stage[actions_by_team_stage.action == "solve"].astype(bool)
tymy_vzaly_napovedu = actions_by_team_stage[(actions_by_team_stage.action == "used") & actions_by_team_stage.index.isin(tymy_vyresily.index)]
tymy_hint_solve = pd.DataFrame({'solve': tymy_vyresily.action, 'hint': tymy_vzaly_napovedu.action.astype(bool)}).fillna(False)
sifry_hint_solve = tymy_hint_solve.groupby("stage").sum()
sifry_hint_solve['hint_perc'] = 100 * sifry_hint_solve.hint / sifry_hint_solve.solve
fig_sifry_hint_solve = sifry_hint_solve['hint_perc'].sort_index(ascending=False).plot.barh(figsize=(10,8))
fig_sifry_hint_solve.set_title("Procento vyřešení s nápovědou")
label_barh(fig_sifry_hint_solve, text_format="{:.2f} %", is_inside=False)
plt.tight_layout()
PRASÁTKA¶
# %% prasatka
prasata = df[df.action == 'piglet'][['team_name', 'piglet_name']]
prasata_stat = prasata.groupby('team_name')['piglet_name'].count()
print('Nejvíce prasátek ({}) získaly týmy: '.format(prasata_stat.max()),
', '.join(prasata_stat[prasata_stat == prasata_stat.max()].index))
Nejpočetnější výletní prasátko si zasloužil tým Set Sail For Fail na šifře 23 - Třebechovice pod Orebem, který pojmul vyzvedávání šifry jako školní výlet s malou šifrovačkou.
Nejvzdálenější Poutnické prasátko získal tým Dělník u vrat za pěší přesun z Rožmitálu do Strakonic (cca 50km a 8h 8min).
Dále Pandy na útěku spolu s Jméno není priorita, kteří došli pěšky na Gryblu (cca 46 km).
print('Statistika prasátek za týmy:')
print('Celkem získalo prasátka {} týmů, v průměru {:.2f} prasátka na tým.'.format(
prasata_stat.count(), prasata_stat.mean(),
))
print('Celkem bylo ve hře získáno {} prasátek.'.format(prasata_stat.sum()))
prasata_per_team = prasata.groupby('piglet_name').count().sort_values('team_name')
prasata_per_team.index.name = 'Prasátko'
prasata_per_team.columns = ['Počet týmů']
fig_prasatka = prasata_per_team.plot.barh(figsize=(8,6))
fig_prasatka.set_title('Jaká prasátka týmy nejvíce získavaly')
label_barh(fig_prasatka, text_format=value_format, is_inside=False)
plt.tight_layout()
VZDÁLENOSTI A RYCHLOSTI¶
# %% Nejvzdálenější špatné řešení
df_all = pd.merge(left=df_actions, right=df_stage, left_on='stage_num', right_on='num')
df_wrong_try = df_all[(df_all.action == 'solve') & (df_all.success == False)].copy()
distances = np.vectorize(haversine)(df_wrong_try['lat_x'], df_wrong_try['lng_x'], df_wrong_try['lat_y'], df_wrong_try['lng_y'])
df_wrong_try['distance'] = distances.tolist()
df_wrong_try = df_wrong_try.set_index('time').tz_localize('CET').sort_values(by='distance')
# nejvetsi ustrely za tymy
worst_tries = crop_std(df_wrong_try.groupby('team_name')['distance'].max())
worst_tries.index.name = 'Týmy'
fig_worst_tries = worst_tries.plot.barh(figsize=(8,4))
fig_worst_tries.set_title('Nejvzdálenější špatné řešení')
plt.tight_layout()
# nejrychlejší přesun mezi stanovišti
df_pickup = df_actions[(df_actions.action == 'pickup') & df_actions.success == True].sort_values(by=['team_name', 'time'])
df_pickup['time_diff'] = df_pickup.time - df_pickup.time.shift()
# ignorujeme predstartovni + start
df_pickup = df_pickup.merge(right=df_stage[df_stage.num > 1], on='stage').set_index('time')
df_pickup['speed'] = df_pickup['distance'] / (df_pickup['time_diff'] / np.timedelta64(1, 'h'))
# ignoruj prechody mezi tymy
df_pickup = df_pickup[df_pickup.speed > 0]
df_pickup = df_pickup[['team_name', 'stage', 'time_diff', 'speed', 'distance']]
df_pickup['time_diff_ms'] = df_pickup.time_diff.astype(np.int64)
# vypocty
nejrychlejsi_presun = df_pickup[df_pickup.speed == df_pickup.speed.max()].iloc[0]
nejkratsi_presun = df_pickup[df_pickup.time_diff == df_pickup.time_diff.min()].iloc[0]
nejpomalejsi_presun = df_pickup[df_pickup.time_diff == df_pickup.time_diff.max()].iloc[0]
print('Potenciální pokutu za rychlou jízdu si vysloužil tým {}\n'
'který při přesunu na šifru {} dosáhl rychlosti přes {} km/hod (vzdušnou čarou!)'.format(
nejrychlejsi_presun.team_name, nejrychlejsi_presun.stage,nejrychlejsi_presun.speed.round()
))
print('\nNejrychleji na další šifru se dostal tým {}\n'
'který přesun na šifru {} vzdálenou {} km zvládl za krásných {} minut'.format(
nejkratsi_presun.team_name, nejkratsi_presun.stage,nejkratsi_presun.distance.round(), nejkratsi_presun.time_diff / np.timedelta64(1, 'm')
))
print('\nNejvytrvalejší ze všech vytrvalých prasátek si vysloužil tým {}\n'
'kteréme přesun na šifru {} zabral úctyhodných {} dní.'.format(
nejpomalejsi_presun.team_name, nejpomalejsi_presun.stage, nejpomalejsi_presun.time_diff.days
))
Speciální uznání za rychlost patří též týmu IQ 43, který se pokusil vyzvednout šifru 21 - Háj u Šumperka měsíc před orgy.
prumerna_rychlost = pd.to_timedelta(df_pickup.groupby('stage').time_diff_ms.mean().sort_values()) / np.timedelta64(1, 'h')
prumerna_rychlost.index.name = 'Šifry'
prumerna_rychlost.name = 'Čas v hodinách'
fig_rychlost = prumerna_rychlost.plot.barh(figsize=(8,6))
fig_rychlost.set_title('Průměrné rychlosti vyzvednutí šifer (v hodinách)')
plt.tight_layout()
# nejrychlejší vyřešení
series_solve = df_actions[(df_actions.action == 'solve') & (df_actions.success == True)]
series_solve = series_solve.set_index(['team_name', 'stage'])['time']
series_solve.name = 'solved'
series_picked = df_actions[(df_actions.action == 'pickup') & (df_actions.success == True)]
series_picked = series_picked.set_index(['team_name', 'stage'])['time']
series_picked.name = 'picked'
df_casy = pd.concat([series_picked, series_solve], axis=1)
df_casy['time_diff'] = df_casy.solved - df_casy.picked
df_casy['time_diff_ms'] = df_casy.time_diff.astype(np.int64)
df_casy = df_casy[df_casy.time_diff_ms > 0]
df_casy.time_diff = df_casy.time_diff.fillna(0)
df_casy = df_casy.reset_index()
Nejrychlejší řešení za šifru¶
nejrychlejsi_reseni = df_casy.loc[df_casy.groupby('stage')['time_diff_ms'].idxmin()].set_index('stage')
df_reseni = nejrychlejsi_reseni[['team_name', 'time_diff']]
df_reseni.index.name = 'Šifry'
df_reseni.columns = ['Tým', 'Čas vyřešení']
df_reseni
def subquery(grp):
def q(df):
df_time = df.time
week = grp[(grp.time > df_time - pd.Timedelta("7 days")) & (grp.time <= df_time)]
time_from, first_stage = week.iloc[0][['time', 'stage']]
time_to, last_stage = week.iloc[-1][['time', 'stage']]
distance = week['distance'][1:].sum()
return pd.Series(dict(
time_from=time_from, first_stage=first_stage,
time_to=time_to, last_stage=last_stage,
distance=distance))
first_last_dist = grp.apply(q, axis=1)
max_dist = first_last_dist.loc[first_last_dist.distance.idxmax()]
max_dist['team_name'] = grp.name
return max_dist
df = df_actions[(df_actions.action == 'pickup') & df_actions.success == True].sort_values(by=['team_name', 'time'])
df['time_diff'] = df.time - df.time.shift()
df = df.merge(right=df_stage[df_stage.num > 1], on='stage')
#df[['first_stage','last_stage','total_distance']] =
pokusy_o_maratonske = df.groupby('team_name', as_index=False, group_keys=False).apply(subquery) \
.set_index("team_name") \
.sort_values(by="distance", ascending=False) \
.loc[lambda it: it.distance > 0]
pokusy_o_maratonske.columns = ["Vzdálenost", "Začátek", "Konec", "Od", "Do"]
pokusy_o_maratonske.index.name = "Tým"
pokusy_o_maratonske