# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: Python 3
#     language: python
#     name: python3
# ---

# ### <NAME>, fivethirtyeight.com, May 13, 2019

# From https://fivethirtyeight.com/features/can-the-riddler-bros-beat-joe-dimaggios-hitting-streak/
#
# Five brothers join the Riddler Baseball Independent Society, or RBIs. Each of them enjoys a lengthy career of 20 seasons, with 160 games per season and four plate appearances per game. (To make this simple, assume each plate appearance results in a hit or an out, so there are no sac flies or walks to complicate this math.)
#
# Given that their batting averages are .200, .250, .300, .350 and .400, what are each brother’s chances of beating DiMaggio’s 56-game hitting streak at some point in his career? (Streaks can span across seasons.)
#
# By the way, their cousin has a .500 average, but he will get tossed from the league after his 10th season when he tests positive for performance enhancers. What are his chances of beating the streak?

# ## Analytical Solution
# Each at-bat is a discrete event. The streak is extended by getting at least one hit in each of those four attempts. The probability of getting a hit in a game as a function of four at-bats:
#
# $Pr(H_{per game}) = Pr(AB1) + Pr(AB2) + Pr(AB3) + Pr(AB4)$
#
# If Pr(H) is the probability of getting a hit and P(O) is the probability of making an out, then:
#
# $Pr(H_{per game}) = Pr(H) + Pr(H)*Pr(O) + Pr(H)*Pr(O)^2 + Pr(H)*Pr(O)^3$
#
# then the probability of starting a streak of **n** games at any given game is:
#
# $Pr(Streak_n) = Pr(H_{per game})^n$
#
# Each game is a new attempt of starting a streak of n games. However, new attempt of an n-game streak must occur n games before the end of the career:
# $ Attempts = Games * Seasons - Streak$
#
# Total probability should be $Attempts * Pr(Streak_n)$ ?

# #### Library Imports

# +
from IPython.display import Image
import pandas as pd
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns
import math
#from tabulate import tabulate
from scipy import stats
sns.set_style('whitegrid')
from decimal import Decimal
from datetime import timedelta

# %matplotlib inline
# -

from functions import * #functions
from genesis_states import  genesis_states
from partial_state_update_block import partial_state_update_block


# ##### Initial Values

#Internal
avg_200 = 200
avg_250 = 250
avg_300 = 300
avg_350 = 350
avg_400 = 400

record = 57
games = 160
seasons = 20
attempts = games * seasons - record
print(attempts)



rbi = pd.DataFrame(data=[avg_200, avg_250, avg_300, avg_350, avg_400], columns=['Player'] )
rbi

from config import *


# Probability of getting a hit in a game as a result of four at bats.

def hit_game(x):
    hit = x / 1000
    out = 1 - hit
    prob = hit + hit * out + hit * out**2 + hit * out**3
    return prob


rbi['per_game'] = rbi.Player.apply(hit_game)
rbi['Streak'] = rbi.per_game**record
rbi['Career'] = rbi.Streak * attempts
rbi['Record'] = rbi.Career / record
rbi

streak_length = rbi.transpose()
streak_length

# Length is a dataframe of the probability of each hitter having a hitting streak of index length using
# their probability of getting a hit per game
length = pd.DataFrame(index =np.arange(1,58,1))
for col in streak_length.columns:
    name = str(streak_length[col].loc['Player'])
    length[name] = streak_length[col].loc['per_game'] ** length.index
length.head()

length.plot(figsize=(10,6))
plt.xlabel('Streak Length')
plt.ylabel('Probability')

length.plot(figsize=(10,6), logy=True)
plt.xlabel('Streak Length')
plt.ylabel('Probability')

# Over a career of attempts

# +
plt.figure(figsize=(14,6))
plt.subplot(1,2,1)
plt.scatter(rbi.Player, rbi.Record)
plt.legend(bbox_to_anchor=(0.75, 0.75), loc=1, borderaxespad=0.)
plt.xlabel('Player Average')
plt.ylabel('Probability')
plt.ylim(0)

plt.subplot(1,2,2)
plt.scatter(rbi.Player, rbi.Career, color ='b')
plt.xlabel('Player Average')
plt.ylabel('Probability')
plt.legend(bbox_to_anchor=(0.75, 0.75), loc=1, borderaxespad=0.)
# -

# ## Numerical Solution
# ### cadCAD Setup
# #### ----------------cadCAD LIBRARY IMPORTS------------------------

df = result

df.head()

# +
# Aggregates df of Monte Carlo runs by timestep. Creates dataframes of mean, median, sd, and minimum.
aggregate_dimension = 'timestep'

mean_df = df.groupby(aggregate_dimension).mean().reset_index()
median_df = df.groupby(aggregate_dimension).median().reset_index()
std_df = df.groupby(aggregate_dimension).std().reset_index()
min_df = df.groupby(aggregate_dimension).min().reset_index()


# -

# #### Plot Functions

def dist_plot(x, y,lx=False,ly=False, suppMin=False):       
    '''
    Generates Monte Carlo plot of mean, median, mean + 1 sd. Option to plot either mean-1 sd or minimum.
    Option to plot log-scale on x or y.
    '''
    plt.figure(figsize=(10,6))
    if not(suppMin):
        plt.plot(mean_df[x].values, mean_df[y].values,
             mean_df[x].values,median_df[y].values,
             mean_df[x].values,mean_df[y].values+std_df[y].values,
             mean_df[x].values,min_df[y].values)
        plt.legend(['mean', 'median', 'mean+ 1*std', 'min'],bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)
        
    else:
        plt.plot(mean_df[x].values, mean_df[y].values,
             mean_df[x].values,median_df[y].values,
             mean_df[x].values,mean_df[y].values+std_df[y].values,
             mean_df[x].values,mean_df[y].values-std_df[y].values)
        plt.legend(['mean', 'median', 'mean+ 1*std', 'mean - 1*std'],bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)

    plt.xlabel(x)
    plt.ylabel(y)
    if lx:
        plt.xscale('log')
    
    if ly:
        plt.yscale('log')


def first_five_plot(state_var):
    '''
    Enter state variable name as a string. Generates timeseries plot of at most the first five
    Monte Carlo runs along with the mean of ALL runs. Use run_count variable name for number of runs.
    '''
    plt.figure(figsize=(10,6))
    if run_count < 5:
        runs = run_count
    else:
        runs = 5
    for r in range(1,runs+1):
        legend_name = 'Run ' + str(r)
        plt.plot(df[df.run==r].timestep, df[df.run==r][state_var], label = legend_name )
    plt.scatter(mean_df.timestep, mean_df[state_var], label = 'Mean', color = 'black')
    plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)
    plt.xlabel('Timestep')
    plt.ylabel(state_var)
    title_text = 'Performance of ' + state_var + ' over the First ' + str(runs) + ' Monte Carlo Runs'
    plt.title(title_text)


def overview_plot(mech_steps, *args, **kwargs):
    '''
    *args, enter df fields of desired plots
    uses time_step_count and run_count variable name from simulation runs.
    **kwarg y_label for custom y axis title
    **kwarg legend_label for custom legend label
    '''
    plt.figure(figsize=(10,6))
    for r in range(run_count):
        plt.axvline(x= mech_steps * time_step_count * r, color ='b')
    for arg in args:
        for key, value in kwargs.items():
            if key == 'y_label':
                plt.ylabel(value)
            if key == 'legend_label':
                plt.plot(arg, label = value)
            else:
                plt.plot(arg) #, label = arg.in
#         plt.plot(arg, label = y_label)
    plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)

    x_text = 'Timestep Repeated over ' + str(run_count) + ' Runs'
    plt.xlabel(x_text)
    plt.title('Overview of Repeated Monte Carlo Runs')


dist_plot('timestep', 'player_200',suppMin=False)

df_sub = df
print('Average Check of .200 = ', len(df_sub[df_sub.player_200 == True]) / len(df_sub))
print('Average Check of .250 = ', len(df_sub[df_sub.player_250 == True]) / len(df_sub))
print('Average Check of .300 = ', len(df_sub[df_sub.player_300 == True]) / len(df_sub))
print('Average Check of .350 = ', len(df_sub[df_sub.player_350 == True]) / len(df_sub))
print('Average Check of .400 = ', len(df_sub[df_sub.player_400 == True]) / len(df_sub))


# +
# class Player():
#     def __init__(self, average):
#         self.average = average
# player_200 = Player(200)
# print(player_200)
# print(player_200.average)
# -

# credit https://stackoverflow.com/questions/52717996/how-can-i-count-the-number-of-consecutive-trues-in-a-dataframe
def maxisland_start_len_mask(a, fillna_index = -1, fillna_len = 0):
    # a is a boolean array

    pad = np.zeros(a.shape[1],dtype=bool)
    mask = np.vstack((pad, a, pad))

    mask_step = mask[1:] != mask[:-1]
    idx = np.flatnonzero(mask_step.T)
    island_starts = idx[::2]
    island_lens = idx[1::2] - idx[::2]
    n_islands_percol = mask_step.sum(0)//2

    bins = np.repeat(np.arange(a.shape[1]),n_islands_percol)
    scale = island_lens.max()+1

    scaled_idx = np.argsort(scale*bins + island_lens)
    grp_shift_idx = np.r_[0,n_islands_percol.cumsum()]
    max_island_starts = island_starts[scaled_idx[grp_shift_idx[1:]-1]]

    max_island_percol_start = max_island_starts%(a.shape[0]+1)

    valid = n_islands_percol!=0
    cut_idx = grp_shift_idx[:-1][valid]
    max_island_percol_len = np.maximum.reduceat(island_lens, cut_idx)

    out_len = np.full(a.shape[1], fillna_len, dtype=int)
    out_len[valid] = max_island_percol_len
    out_index = np.where(valid,max_island_percol_start,fillna_index)
    return out_index, out_len
# credit https://stackoverflow.com/questions/52717996/how-can-i-count-the-number-of-consecutive-trues-in-a-dataframe


# +
streak_df = pd.DataFrame(index=range(1,run_count+1),columns=['Player_200', 'Player_250','Player_300','Player_350','Player_400'] )

for r in range(1,run_count+1):
    season_df = df[(df.substep == 4) & (df.run == r)]
  
    a = np.array([season_df.game_200, season_df.game_250,season_df.game_300,season_df.game_350,season_df.game_400])
    a = np.transpose(a)
    streak_start, streak_length = maxisland_start_len_mask(a, fillna_index=-1, fillna_len=0)

    for index, col in enumerate(streak_df.columns):

        streak_df[col][r] = streak_length[index]
# -

a = streak_df.Player_400[streak_df.Player_400 > 56].count()
b = streak_df.Player_350[streak_df.Player_350 > 56].count()
c = streak_df.Player_300[streak_df.Player_300 > 56].count()
d = streak_df.Player_250[streak_df.Player_250 > 56].count()
e = streak_df.Player_200[streak_df.Player_200 > 56].count()
print('400 times = ', a, ' over ', run_count, ' simulated careers or ', a * 100 / run_count, '%' )
print('350 times = ', b, ' over ', run_count, ' simulated careers or ', b * 100 / run_count, '%' )
print('300 times = ', c, ' over ', run_count, ' simulated careers or ', c * 100 / run_count, '%' )
print('250 times = ', d, ' over ', run_count, ' simulated careers or ', d * 100 / run_count, '%' )
print('200 times = ', e, ' over ', run_count, ' simulated careers or ', e * 100 / run_count, '%' )
print('Total = ', streak_df[streak_df.values > 56].count().values[0])

streak_df.plot(figsize=(14,6))
plt.axhline(y= 56, color ='b', label='Joe D Record')
plt.ylabel('Longest Streak by Player')
plt.xlabel('Simulated Career')
title_text = 'Maximum Length of Career Hitting Streak over ' + str(run_count) + ' Simulated Careers'
plt.title(title_text)

plt.figure(figsize=(14,6))
plt.bar(streak_df.index-0.5, streak_df.Player_200, width=0.25,  label ='Player_200') 
plt.bar(streak_df.index-0.25, streak_df.Player_250, width=0.25, label ='Player_250') 
plt.bar(streak_df.index, streak_df.Player_300, width=0.25, label ='Player_300') 
plt.bar(streak_df.index + 0.25, streak_df.Player_350, width=0.25, label ='Player_350') 
plt.bar(streak_df.index + 0.5, streak_df.Player_400, width=0.25, label ='Player_400') 
plt.axhline(y= 56, color ='b', label='<NAME>')
plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)
plt.ylabel('Longest Streak by Player')
plt.xlabel('Simulated Career')
title_text = 'Maximum Length of Career Hitting Streak over ' + str(run_count) + ' Simulated Careers'
plt.title(title_text)
plt.show()

g = sns.catplot( data=streak_df,
                height=6, kind="bar", palette="muted")
g.set_ylabels("Max Streak by Career")
title_text = 'Distribution of Longest Hitting Streak over  ' + str(run_count) + ' Simulated Careers'
plt.title(title_text)
plt.show()

streak_df.head()

mean_df = streak_df.mean()
median_df = streak_df.median()
min_df = streak_df.min()
max_df = streak_df.max()
std_plus_df = mean_df + streak_df.std()
std_minus_df = mean_df - streak_df.std()

max_df.plot(figsize=(10,6))
std_plus_df.plot()
mean_df.plot()
median_df.plot()
std_minus_df.plot()
min_df.plot()
plt.xticks([0,1,2,3,4],['.200','.250','.300','.350','.400'])
plt.legend(['max','mean + 1 SD','mean', 'median','mean - 1 SD','min'],bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)
plt.ylabel('Hitting Streak Length')
plt.xlabel('Batting Average')
title_text = 'Statistical Performance by Player BA over ' + str(run_count) + ' Simulated Careers'
plt.title(title_text)
plt.show()



StarcoderdataJupyterScriptsDedupFiltered