예제 #1
0
def _get_game_h2h_chart_title(season, game, homecf_diff=None, totaltoi=None):
    """
    Returns the title for the H2H chart
    :param season: int, the season
    :param game: int, the game
    :param homecf_diff: int. The home team corsi advantage
    :param totaltoi: int. The TOI played so far.
    :return:
    """
    titletext = []
    # Note if a game was OT or SO
    otso_str = ss.get_game_result(season, game)
    if otso_str[:2] == 'OT' or otso_str[:2] == 'SO':
        otso_str = ' ({0:s})'.format(otso_str[:2])
    else:
        otso_str = ''
    # Add strings to a list then join them together with newlines
    titletext.append('H2H Corsi and TOI for {0:d}-{1:s} Game {2:d}'.format(season, str(season + 1)[2:], game))
    titletext.append('{0:s} {1:d} at {2:s} {3:d}{4:s} ({5:s})'.format(
        ss.team_as_str(ss.get_road_team(season, game), abbreviation=False),
        ss.get_road_score(season, game),
        ss.team_as_str(ss.get_home_team(season, game), abbreviation=False),
        ss.get_home_score(season, game),
        otso_str, ss.get_game_status(season, game)))
    if homecf_diff is not None and totaltoi is not None:
        titletext.append('{0:s} {1:s} in 5v5 attempts in {2:s}'.format(
            ss.team_as_str(ss.get_home_team(season, game)),
            _format_number_with_plus(int(homecf_diff)), manip.time_to_mss(int(totaltoi))))
    return '\n'.join(titletext)
예제 #2
0
def game_game_index(season, game):
    gameinfo = ss.get_game_data_from_schedule(season, game)
    hname = ss.team_as_str(gameinfo['Home'])
    rname = ss.team_as_str(gameinfo['Road'])
    links = {
        charttype: get_game_chart_page_url(season, game, charttype)
        for charttype in get_active_game_chart_types()
    }
    return render_template(
        'index.html',
        linklist=links,
        pagetitle='{0:d}-{1:s} {2:d}\n{3:s} at {4:s}\n{5:s}'.format(
            season,
            str(season + 1)[2:], game, rname, hname, gameinfo['Date']))
예제 #3
0
def generate_player_toion_toioff(season):
    """
    Generates TOION and TOIOFF at 5v5 for each player in this season.
    :param season: int, the season
    :return: df with columns Player, TOION, TOIOFF, and TOI60.
    """

    spinner = halo.Halo()
    spinner.start(text='Generating TOI60 for {0:d}'.format(season))

    team_by_team = []
    allteams = ss.get_teams_in_season(season)
    for i, team in enumerate(allteams):
        if os.path.exists(ss.get_team_toi_filename(season, team)):
            spinner.start(text='Generating TOI60 for {0:d} {1:s} ({2:d}/{3:d})'.format(
                season, ss.team_as_str(team), i + 1, len(allteams)))
            toi_indiv = get_5v5_player_season_toi(season, team)
            team_by_team.append(toi_indiv)
            spinner.stop()

    toi60 = pd.concat(team_by_team)
    toi60 = toi60.groupby('PlayerID').sum().reset_index()
    toi60.loc[:, 'TOI%'] = toi60.TOION / (toi60.TOION + toi60.TOIOFF)
    toi60.loc[:, 'TOI60'] = toi60['TOI%'] * 60

    return toi60
예제 #4
0
def game_season_index(season):
    if season == ss.get_current_season():
        ss.generate_season_schedule_file(season)
        ss.refresh_schedules()
    sch = ss.get_season_schedule(season)

    sch.loc[:, 'Home'] = sch.Home.apply(lambda x: ss.team_as_str(x))
    sch.loc[:, 'Road'] = sch.Road.apply(lambda x: ss.team_as_str(x))
    sch = sch[['Date', 'Game', 'Home', 'Road', 'HomeScore', 'RoadScore', 'Status', 'Result']] \
        .rename(columns={'Result': 'HomeResult'})
    sch.loc[:, 'Charts'] = sch.Game.apply(lambda x: get_game_url(season, x))

    return render_template('games.html',
                           season=season,
                           schedule=sch,
                           pagetitle='Games in {0:d}-{1:s}'.format(
                               season,
                               str(season + 1)[2:]))
예제 #5
0
def _get_corsi_timeline_title(season, game):
    """
    Returns the default chart title for corsi timelines.
    :param season: int, the season
    :param game: int, the game
    :return: str, the title
    """
    otso_str = ss.get_game_result(season, game)
    if otso_str[:2] == 'OT' or otso_str[:2] == 'SO':
        otso_str = ' ({0:s})'.format(otso_str[:2])
    else:
        otso_str = ''
    # Add strings to a list then join them together with newlines
    titletext = ('Shot attempt timeline for {0:d}-{1:s} Game {2:d} ({3:s})'.format(
        int(season), str(int(season + 1))[2:], int(game), ss.get_game_date(season, game)),
                 '{0:s} {1:d} at {2:s} {3:d}{4:s} ({5:s})'.format(
        ss.team_as_str(ss.get_road_team(season, game), abbreviation=False),
        ss.get_road_score(season, game),
        ss.team_as_str(ss.get_home_team(season, game), abbreviation=False),
        ss.get_home_score(season, game),
        otso_str, ss.get_game_status(season, game)))

    return '\n'.join(titletext)
예제 #6
0
def generate_toicomp(season):
    """
    Generates toicomp at a player-game level
    :param season: int, the season
    :return: df,
    """

    spinner = halo.Halo()
    spinner.start(text='Generating TOICOMP for {0:d}'.format(season))

    team_by_team = []
    allteams = ss.get_teams_in_season(season)
    for i, team in enumerate(allteams):
        if os.path.exists(ss.get_team_toi_filename(season, team)):
            spinner.start(text='Generating TOICOMP for {0:d} {1:s} ({2:d}/{3:d})'.format(
                season, ss.team_as_str(team), i + 1, len(allteams)))

            qct = get_5v5_player_game_toicomp(season, team)
            if qct is not None:
                team_by_team.append(qct)
            spinner.stop()

    df = pd.concat(team_by_team)
    return df
예제 #7
0
def game_timeline(season, game, save_file=None):
    """
    Creates a shot attempt timeline as seen on @muneebalamcu
    :param season: int, the season
    :param game: int, the game
    :param save_file: str, specify a valid filepath to save to file. If None, merely shows on screen. Specify 'fig'
    to return the figure
    :return: nothing
    """
    plt.clf()

    hname = ss.team_as_str(ss.get_home_team(season, game))
    rname = ss.team_as_str(ss.get_road_team(season, game))

    cf = {hname: _get_home_cf_for_timeline(season, game), rname: _get_road_cf_for_timeline(season, game)}
    pps = {hname: _get_home_adv_for_timeline(season, game), rname: _get_road_adv_for_timeline(season, game)}
    gs = {hname: _get_home_goals_for_timeline(season, game), rname: _get_road_goals_for_timeline(season, game)}
    colors = {hname: plt.rcParams['axes.prop_cycle'].by_key()['color'][0],
              rname: plt.rcParams['axes.prop_cycle'].by_key()['color'][1]}
    darkercolors = {team: _make_color_darker(hex=col) for team, col in colors.items()}

    # Corsi lines
    for team in cf:
        plt.plot(cf[team].Time, cf[team].CumCF, label=team, color=colors[team])

    # Ticks every 10 min
    plt.xticks(range(0, cf[hname].Time.max() + 1, 10))
    plt.xlabel('Time elapsed in game (min)')

    # Label goal counts when scored with diamonds
    for team in gs:
        xs, ys = _goal_times_to_scatter_for_timeline(gs[team], cf[team])
        plt.scatter(xs, ys, edgecolors='k', marker='D', label='{0:s} goal'.format(team), zorder=3, color=colors[team])

    # Bold lines to separate periods
    _, ymax = plt.ylim()
    for x in range(0, cf[hname].Time.max(), 20):
        plt.plot([x, x], [0, ymax], color='k', lw=2)

    # PP highlighting
    # Note that axvspan works in relative coords (0 to 1), so need to divide by ymax
    for team in pps:
        for pptype in pps[team]:
            if pptype[-2:] == '+1':
                colors_to_use = colors
            else:
                colors_to_use = darkercolors
            for i, (start, end) in enumerate(pps[team][pptype]):
                cf_at_time_min = cf[team].loc[cf[team].Time == start//60].CumCF.iloc[0] - 2
                if end // 60 == cf[team].Time.max():  # might happen for live games
                    cf_at_time_max = cf[team][cf[team].Time == end // 60].CumCF.iloc[0] + 2
                else:
                    cf_at_time_max = cf[team][cf[team].Time == end // 60 + 1].CumCF.iloc[0] + 2
                if i == 0:
                    plt.gca().axvspan(start / 60, end / 60, ymin=cf_at_time_min/ymax,
                                      ymax=cf_at_time_max/ymax, alpha=0.5, facecolor=colors_to_use[team],
                                      label='{0:s} {1:s}'.format(team, pptype))
                else:
                    plt.gca().axvspan(start / 60, end / 60, ymin=cf_at_time_min/ymax,
                                      ymax=cf_at_time_max/ymax, alpha=0.5, facecolor=colors[team])
                plt.gca().axvspan(start / 60, end / 60, ymin=0, ymax=0.05, alpha=0.5, facecolor=colors_to_use[team])

    # Set limits
    plt.xlim(0, cf[hname].Time.max())
    plt.ylim(0, ymax)
    plt.ylabel('Cumulative CF')
    plt.legend(loc=2, framealpha=0.5, fontsize=8)

    # Set title
    plt.title(_get_corsi_timeline_title(season, game))

    if save_file is None:
        plt.show()
    elif save_file == 'fig':
        return plt.gcf()
    else:
        plt.savefig(save_file)
예제 #8
0
def _game_h2h_chart(season, game, corsi, toi, orderh, orderr, numf_h=None, numf_r=None, save_file=None):
    """

    :param season: int, the season
    :param game: int, the game
    :param
    :param corsi: df of P1, P2, Corsi +/- for P1
    :param toi: df of P1, P2, H2H TOI
    :param orderh: list of float, player order on y-axis, top to bottom
    :param orderr: list of float, player order on x-axis, left to right
    :param numf_h: int. Number of forwards for home team. Used to add horizontal bold line between F and D
    :param numf_r: int. Number of forwards for road team. Used to add vertical bold line between F and D.
    :param save_file: str of file to save the figure to, or None to simply display
    :return: nothing
    """

    hname = ss.team_as_str(ss.get_home_team(season, game), True)
    homename = ss.team_as_str(ss.get_home_team(season, game), False)
    rname = ss.team_as_str(ss.get_road_team(season, game), True)
    roadname = ss.team_as_str(ss.get_road_team(season, game), False)

    fig, ax = plt.subplots(1, figsize=[11, 7])

    # Convert dataframes to coordinates
    horderdf = pd.DataFrame({'PlayerID1': orderh[::-1], 'Y': list(range(len(orderh)))})
    rorderdf = pd.DataFrame({'PlayerID2': orderr, 'X': list(range(len(orderr)))})
    plotdf = toi.merge(corsi, how='left', on=['PlayerID1', 'PlayerID2']) \
        .merge(horderdf, how='left', on='PlayerID1') \
        .merge(rorderdf, how='left', on='PlayerID2')

    # Hist2D of TOI
    # I make the bins a little weird so my coordinates are centered in them. Otherwise, they're all on the edges.
    _, _, _, image = ax.hist2d(x=plotdf.X, y=plotdf.Y, bins=(np.arange(-0.5, len(orderr) + 0.5, 1),
                                                             np.arange(-0.5, len(orderh) + 0.5, 1)),
                               weights=plotdf.Min, cmap=plt.cm.summer)

    # Convert IDs to names and label axes and axes ticks
    ax.set_xlabel(roadname)
    ax.set_ylabel(homename)
    xorder = ss.playerlst_as_str(orderr)
    yorder = ss.playerlst_as_str(orderh)[::-1]  # need to go top to bottom, so reverse order
    ax.set_xticks(range(len(xorder)))
    ax.set_yticks(range(len(yorder)))
    ax.set_xticklabels(xorder, fontsize=10, rotation=45, ha='right')
    ax.set_yticklabels(yorder, fontsize=10)
    ax.set_xlim(-0.5, len(orderr) - 0.5)
    ax.set_ylim(-0.5, len(orderh) - 0.5)

    # Hide the little ticks on the axes by setting their length to 0
    ax.tick_params(axis='both', which='both', length=0)

    # Add dividing lines between rows
    for x in np.arange(0.5, len(orderr) - 0.5, 1):
        ax.plot([x, x], [-0.5, len(orderh) - 0.5], color='k')
    for y in np.arange(0.5, len(orderh) - 0.5, 1):
        ax.plot([-0.5, len(orderr) - 0.5], [y, y], color='k')

    # Add a bold line between F and D.
    if numf_r is not None:
        ax.plot([numf_r - 0.5, numf_r - 0.5], [-0.5, len(orderh) - 0.5], color='k', lw=3)
    if numf_h is not None:
        ax.plot([-0.5, len(orderr) - 0.5], [len(orderh) - numf_h - 0.5, len(orderh) - numf_h - 0.5], color='k', lw=3)

    # Colorbar for TOI
    cbar = fig.colorbar(image, pad=0.1)
    cbar.ax.set_ylabel('TOI (min)')

    # Add trademark
    cbar.ax.set_xlabel('Muneeb Alam\n@muneebalamcu', labelpad=20)

    # Add labels for Corsi and circle negatives
    neg_x = []
    neg_y = []
    for y in range(len(orderh)):
        hpid = orderh[len(orderh) - y - 1]
        for x in range(len(orderr)):
            rpid = orderr[x]

            cf = corsi[(corsi.PlayerID1 == hpid) & (corsi.PlayerID2 == rpid)]
            if len(cf) == 0:  # In this case, player will not have been on ice for a corsi event
                cf = 0
            else:
                cf = int(cf.HomeCorsi.iloc[0])

            if cf == 0:
                cf = '0'
            elif cf > 0:
                cf = '+' + str(cf)  # Easier to pick out positives with plus sign
            else:
                cf = str(cf)
                neg_x.append(x)
                neg_y.append(y)

            ax.annotate(cf, xy=(x, y), ha='center', va='center')

    # Circle negative numbers by making a scatterplot with black edges and transparent faces
    ax.scatter(neg_x, neg_y, marker='o', edgecolors='k', s=200, facecolors='none')

    # Add TOI and Corsi totals at end of rows/columns
    topax = ax.twiny()
    topax.set_xticks(range(len(xorder)))
    rtotals = pd.DataFrame({'PlayerID2': orderr}) \
        .merge(toi[['PlayerID2', 'Secs']].groupby('PlayerID2').sum().reset_index(),
               how='left', on='PlayerID2') \
        .merge(corsi[['PlayerID2', 'HomeCorsi']].groupby('PlayerID2').sum().reset_index(),
               how='left', on='PlayerID2')
    rtotals.loc[:, 'HomeCorsi'] = rtotals.HomeCorsi.fillna(0)
    rtotals.loc[:, 'CorsiLabel'] = rtotals.HomeCorsi.apply(lambda x: _format_number_with_plus(-1 * int(x / 5)))
    rtotals.loc[:, 'TOILabel'] = rtotals.Secs.apply(lambda x: manip.time_to_mss(x / 5))
    toplabels = ['{0:s} in {1:s}'.format(x, y) for x, y, in zip(list(rtotals.CorsiLabel), list(rtotals.TOILabel))]

    ax.set_xticks(range(len(xorder)))
    topax.set_xticklabels(toplabels, fontsize=6, rotation=45, ha='left')
    topax.set_xlim(-0.5, len(orderr) - 0.5)
    topax.tick_params(axis='both', which='both', length=0)

    rightax = ax.twinx()
    rightax.set_yticks(range(len(yorder)))
    htotals = pd.DataFrame({'PlayerID1': orderh[::-1]}) \
        .merge(toi[['PlayerID1', 'Secs']].groupby('PlayerID1').sum().reset_index(),
               how='left', on='PlayerID1') \
        .merge(corsi[['PlayerID1', 'HomeCorsi']].groupby('PlayerID1').sum().reset_index(),
               how='left', on='PlayerID1')
    htotals.loc[:, 'HomeCorsi'] = htotals.HomeCorsi.fillna(0)
    htotals.loc[:, 'CorsiLabel'] = htotals.HomeCorsi.apply(lambda x: _format_number_with_plus(int(x / 5)))
    htotals.loc[:, 'TOILabel'] = htotals.Secs.apply(lambda x: manip.time_to_mss(x / 5))
    rightlabels = ['{0:s} in {1:s}'.format(x, y) for x, y, in zip(list(htotals.CorsiLabel), list(htotals.TOILabel))]

    rightax.set_yticks(range(len(yorder)))
    rightax.set_yticklabels(rightlabels, fontsize=6)
    rightax.set_ylim(-0.5, len(orderh) - 0.5)
    rightax.tick_params(axis='both', which='both', length=0)

    # plt.subplots_adjust(top=0.80)
    # topax.set_ylim(-0.5, len(orderh) - 0.5)

    # Add brief explanation for the top left cell at the bottom
    explanation = []
    row1name = yorder.iloc[-1]
    col1name = xorder.iloc[0]
    timeh2h = int(toi[(toi.PlayerID1 == orderh[0]) & (toi.PlayerID2 == orderr[0])].Secs.iloc[0])
    shoth2h = int(corsi[(corsi.PlayerID1 == orderh[0]) & (corsi.PlayerID2 == orderr[0])].HomeCorsi.iloc[0])

    explanation.append('The top left cell indicates {0:s} (row 1) faced {1:s} (column 1) for {2:s}.'.format(
        row1name, col1name, manip.time_to_mss(timeh2h)))
    if shoth2h == 0:
        explanation.append('During that time, {0:s} and {1:s} were even in attempts.'.format(hname, rname))
    elif shoth2h > 0:
        explanation.append('During that time, {0:s} out-attempted {1:s} by {2:d}.'.format(hname, rname, shoth2h))
    else:
        explanation.append('During that time, {1:s} out-attempted {0:s} by {2:d}.'.format(hname, rname, -1 * shoth2h))
    explanation = '\n'.join(explanation)

    # Hacky way to annotate: add this to x-axis label
    ax.set_xlabel(ax.get_xlabel() + '\n\n' + explanation)

    plt.subplots_adjust(bottom=0.27)
    plt.subplots_adjust(left=0.17)
    plt.subplots_adjust(top=0.82)
    plt.subplots_adjust(right=1.0)

    # Add title
    plt.title(_get_game_h2h_chart_title(season, game, corsi.HomeCorsi.sum() / 25, toi.Secs.sum() / 25),
              y=1.1, va='bottom')

    # fig.tight_layout()
    if save_file is None:
        plt.show()
    elif save_file == 'fig':
        return plt.gcf()
    else:
        plt.savefig(save_file)