Menu

Statistiky

Zdrojová data pro statistiky stahujte zde. Můžete se pak vlastními výstupy z dat pochlubit v reportážích :)

In [1]:
%matplotlib inline
import warnings; warnings.simplefilter('ignore')
In [2]:
"""
++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')
In [3]:
# %% const
CELKEM_TYMU = 239
In [4]:
# %% 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
In [5]:
# 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))
In [6]:
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)
In [7]:
# %% 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'])
In [8]:
# %% 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)
In [9]:
# %% 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)
In [10]:
# %% merge all
df = pd.concat([df_actions, hint_actions], axis=0, sort=True)
df.index = df.index.tz_localize('CET')

NÁPOVĚDY

In [11]:
# %% 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))  
Celkem bylo 6 nápovědních kódů: krokodyl, velemlok, hunek, brundibar, krajka, hotentot

Kódy získalo 83 týmů.

Nejvíce nevyužitých kódů (4) vlastní tým:  Set sail for fail

Všech 6 kódů získaly týmy:  Set sail for fail, Enigmafie a přátelé

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.

In [12]:
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

In [13]:
# %% 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()
In [14]:
# %% 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')]
In [15]:
# %% 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()
In [16]:
# %% 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()
In [17]:
# %% 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()))
Dny s maximální aktivitou:  2017-06-04
Dny bez aktivity:  2017-09-13

HALUZENÍ

In [18]:
# %% 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()
In [19]:
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()
In [20]:
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

In [21]:
# %% 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()
In [22]:
# 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

In [23]:
# %% 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))
Nejvíce prasátek (26) získaly týmy:  3 s kouskem fosforu (dříve vápníku), Enigmafie a přátelé, Opravte Tykadla, Set sail for fail, Spící Volové, Squeak!, Zlá koťata

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).

In [24]:
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()))
Statistika prasátek za týmy:
Celkem získalo prasátka 112 týmů, v průměru 8.19 prasátka na tým.
Celkem bylo ve hře získáno 917 prasátek.
In [25]:
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

In [26]:
# %% 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()
In [27]:
# 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
))
Potenciální pokutu za rychlou jízdu si vysloužil tým Nekonečná směs
který při přesunu na šifru 19 - Litovel dosáhl rychlosti přes 71.0 km/hod (vzdušnou čarou!)

Nejrychleji na další šifru se dostal tým Divize nulou
který přesun na šifru 13 - Doubravice vzdálenou 23.0 km zvládl za krásných 33.0 minut

Nejvytrvalejší ze všech vytrvalých prasátek si vysloužil tým Gottland
kteréme přesun na šifru 03 - Chotouň zabral úctyhodných 345 dní.

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.

In [28]:
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()
In [29]:
# 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

In [30]:
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
Out[30]:
Tým Čas vyřešení
Šifry
01 - Start Krabice ve slevě 0 days 00:41:00
02 - Grybla Team Avatar 4 days 21:48:00
03 - Chotouň Výletníci 0 days 02:17:00
04 - Rožmitál pod Třemšínem Zvířátka 0 days 00:14:00
05 - Strakonice Rychlá rota 0 days 00:09:00
06 - Písek Lumpík 0 days 00:50:00
07 - Tábor Zlá koťata 0 days 00:33:00
08 - České Budějovice Squeak! 0 days 01:04:00
09 - Staré Město pod Landštejnem Opravte Tykadla 0 days 04:25:00
10 - Metánov Zvířátka 0 days 03:27:00
11 - Křivý Javor Želvonohý 0 days 02:18:00
12 - Jevíčko Set sail for fail 0 days 00:05:00
13 - Doubravice Kočky 0 days 03:40:00
14 - Tišnov Freezy Bee 0 days 00:40:00
15 - Veverská Bítýška Spící Volové 0 days 00:59:00
16 - Kočičí Kámen Freezy Bee 0 days 02:21:00
17 - Modrá Výletníci 0 days 00:43:00
18 - Kelčský Javorník Set sail for fail 0 days 00:14:00
19 - Litovel Spící Volové 0 days 00:55:00
20 - Habermannův mlýn Želvonohý 0 days 00:45:00
21 - Háj u Šumperka Freezy Bee 0 days 01:15:00
22 - Prostřední Lipka Spící Volové 0 days 02:00:00
23 - Třebechovice pod Orebem Nekonečná směs 0 days 08:26:00
24 - Ještěd Iq43 0 days 00:26:00
25 - Rytířov 3 s kouskem fosforu (dříve vápníku) 0 days 01:38:00
26 - Velká Volavka Danina Banda 0 days 03:10:00
In [52]:
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
Out[52]:

Nejlepší maratonský běh

Vzdálenost Začátek Konec Od Do
Tým
Set sail for fail 344.132638 11 - Křivý Javor 20 - Habermannův mlýn 2018-02-11 12:40:00 2018-02-15 15:53:00
Kočky 316.626142 11 - Křivý Javor 19 - Litovel 2018-01-26 23:17:00 2018-02-02 10:45:00
Zlá koťata 281.480512 07 - Tábor 13 - Doubravice 2017-08-26 15:20:00 2017-09-01 12:39:00
Freezy Bee 277.243596 14 - Tišnov 22 - Prostřední Lipka 2018-03-04 10:20:00 2018-03-10 15:31:00
Spící Volové 259.420821 18 - Kelčský Javorník 24 - Ještěd 2018-03-30 18:30:00 2018-04-03 15:51:00
Squeak! 258.504330 14 - Tišnov 21 - Háj u Šumperka 2018-05-13 20:20:00 2018-05-20 11:34:00
3 s kouskem fosforu (dříve vápníku) 257.048080 23 - Třebechovice pod Orebem 27 - Holešovice 2018-05-26 17:16:00 2018-05-31 19:54:00
Enigmafie a přátelé 257.048080 23 - Třebechovice pod Orebem 27 - Holešovice 2018-05-25 12:18:00 2018-05-30 16:11:00
Iq43 257.048080 23 - Třebechovice pod Orebem 27 - Holešovice 2018-04-27 10:00:00 2018-05-03 16:42:00
Kadidla 257.048080 23 - Třebechovice pod Orebem 27 - Holešovice 2018-05-27 15:38:00 2018-06-02 22:32:00
Nekonečná směs 253.614677 14 - Tišnov 20 - Habermannův mlýn 2018-05-18 10:18:00 2018-05-24 18:37:00
Brouk v hlavě 253.614677 14 - Tišnov 20 - Habermannův mlýn 2017-11-24 13:40:00 2017-12-01 11:15:00
Opravte Tykadla 250.022005 15 - Veverská Bítýška 21 - Háj u Šumperka 2018-03-20 09:00:00 2018-03-22 15:14:00
Výletníci 250.022005 15 - Veverská Bítýška 21 - Háj u Šumperka 2018-05-20 12:52:00 2018-05-27 11:37:00
Želvonohý 250.022005 15 - Veverská Bítýška 21 - Háj u Šumperka 2018-05-25 19:54:00 2018-05-31 05:40:00
Modré medúzy 164.340055 24 - Ještěd 27 - Holešovice 2018-05-07 17:27:00 2018-05-12 14:31:00
Danina Banda 164.340055 24 - Ještěd 27 - Holešovice 2018-05-21 17:10:00 2018-05-24 10:33:00
Lumpík 146.961042 04 - Rožmitál pod Třemšínem 08 - České Budějovice 2018-01-27 10:35:00 2018-02-02 18:05:00
Zvířátka 146.961042 04 - Rožmitál pod Třemšínem 08 - České Budějovice 2018-02-08 09:46:00 2018-02-14 16:53:00
Šifra sem, šifra tam. 144.992399 10 - Metánov 13 - Doubravice 2017-08-19 12:00:00 2017-08-21 17:49:00
Dost et al. 124.160589 14 - Tišnov 17 - Modrá 2018-01-14 13:40:00 2018-01-20 11:28:00
Společenští poustevníci 117.363379 03 - Chotouň 06 - Písek 2017-07-10 00:03:00 2017-07-16 16:20:00
Kanonický divisor 117.363379 03 - Chotouň 06 - Písek 2017-06-19 01:13:00 2017-06-25 16:34:00
Distribuovaná DuJour a širší spol. 117.363379 03 - Chotouň 06 - Písek 2017-08-27 11:15:00 2017-08-29 17:47:00
Šmeydíci 111.570045 25 - Rytířov 27 - Holešovice 2018-05-04 12:41:00 2018-05-11 09:53:00
VIND 96.797730 04 - Rožmitál pod Třemšínem 07 - Tábor 2018-02-01 16:16:00 2018-02-07 15:38:00
Potkali se u divokého ninji Kurta Gödela 96.797730 04 - Rožmitál pod Třemšínem 07 - Tábor 2018-02-09 00:01:00 2018-02-11 13:20:00
Rychlá rota 96.797730 04 - Rožmitál pod Třemšínem 07 - Tábor 2018-01-06 16:51:00 2018-01-07 13:41:00
Korupce na Slunci 96.797730 04 - Rožmitál pod Třemšínem 07 - Tábor 2017-07-30 17:43:00 2017-08-02 09:14:00
Máme tušení 96.797730 04 - Rožmitál pod Třemšínem 07 - Tábor 2018-05-08 11:24:00 2018-05-10 09:43:00
Dělník u vrat 96.797730 04 - Rožmitál pod Třemšínem 07 - Tábor 2018-02-18 16:56:00 2018-02-25 15:05:00
JménoNeníPriorita 94.138760 03 - Chotouň 05 - Strakonice 2017-07-19 18:06:00 2017-07-21 14:18:00
abpoWOOL 94.138760 03 - Chotouň 05 - Strakonice 2017-06-17 00:06:00 2017-06-23 12:21:00
Točená zmrzlina Na výletě 94.138760 03 - Chotouň 05 - Strakonice 2017-12-16 15:50:00 2017-12-22 19:54:00
Divize nulou 72.228472 11 - Křivý Javor 13 - Doubravice 2018-05-31 10:00:00 2018-06-01 14:01:00
ŽaHrabáČimiči 64.020406 16 - Kočičí Kámen 17 - Modrá 2018-05-20 14:44:00 2018-05-26 12:28:00
Krabice ve slevě 61.310165 04 - Rožmitál pod Třemšínem 06 - Písek 2017-09-02 11:22:00 2017-09-02 15:56:00
Kostel sv. Jiljí 61.310165 04 - Rožmitál pod Třemšínem 06 - Písek 2017-07-06 16:19:00 2017-07-06 20:25:00
Nejnekvazidiagonalizovávatelnější 61.310165 04 - Rožmitál pod Třemšínem 06 - Písek 2018-01-07 14:47:00 2018-01-07 19:30:00
Divocí Tučňáci 61.310165 04 - Rožmitál pod Třemšínem 06 - Písek 2017-06-23 11:00:00 2017-06-23 20:56:00
Tři Petři & spol. 61.310165 04 - Rožmitál pod Třemšínem 06 - Písek 2018-04-15 09:39:00 2018-04-15 14:37:00
Černý racek 61.310165 04 - Rožmitál pod Třemšínem 06 - Písek 2017-06-30 14:45:00 2017-06-30 18:17:00
Abakus 61.310165 04 - Rožmitál pod Třemšínem 06 - Písek 2017-07-09 17:43:00 2017-07-15 15:00:00
Piliny všude okolo 56.053214 03 - Chotouň 04 - Rožmitál pod Třemšínem 2017-08-01 16:23:00 2017-08-04 10:15:00
Hakuna Matata 51.657858 15 - Veverská Bítýška 16 - Kočičí Kámen 2018-05-13 14:52:00 2018-05-19 10:40:00
Notorická veverka 50.163313 07 - Tábor 08 - České Budějovice 2018-04-22 15:57:00 2018-04-28 18:57:00
Spanilá jízda 50.163313 07 - Tábor 08 - České Budějovice 2018-02-18 15:07:00 2018-02-20 16:37:00
Karel 50.163313 07 - Tábor 08 - České Budějovice 2017-09-10 15:08:00 2017-09-17 14:21:00
01 & netera 50.163313 07 - Tábor 08 - České Budějovice 2017-10-04 14:41:00 2017-10-09 17:06:00
JTHTN 50.163313 07 - Tábor 08 - České Budějovice 2017-10-29 13:30:00 2017-11-02 16:27:00
Tleskačovo kolo 38.085546 04 - Rožmitál pod Třemšínem 05 - Strakonice 2018-02-03 18:55:00 2018-02-04 15:51:00
VZJ 38.085546 04 - Rožmitál pod Třemšínem 05 - Strakonice 2017-08-06 12:16:00 2017-08-13 10:03:00
ŠlukLokFuk 38.085546 04 - Rožmitál pod Třemšínem 05 - Strakonice 2017-07-30 17:09:00 2017-08-06 17:05:00
Nic Nás Nenapadlo 38.085546 04 - Rožmitál pod Třemšínem 05 - Strakonice 2017-07-30 13:20:00 2017-07-30 15:25:00