Costruire sistemi di scommesse con Python, Panda e Streamlit (NHL)

Jan 20 2023
Un'introduzione su come utilizzare Python, Pandas e Streamlit per creare un prodotto di dati vivente.
Sommario: — Introduzione — Estrai — Trasforma — Distribuisci — Postfazione: perché i dati sportivi? TLDR; Ecco un collegamento al repository GitHub con tutto il codice indicato di seguito e un collegamento all'app Streamlit funzionante. Introduzione L'inverno è lungo negli Stati Uniti nordorientali.

Sommario:

— Introduzione

— Estratto

— Trasforma

— Distribuisci

— Postfazione: perché i dati sportivi?

TLDR ; Ecco un collegamento al repository GitHub con tutto il codice indicato di seguito e un collegamento all'app Streamlit funzionante.

Architettura di base della soluzione

introduzione

L'inverno è lungo negli Stati Uniti nordorientali. Fa freddo, fa buio alle 17:00 e le giornate sono grigie e tristi. Uno dei lati positivi dell'inverno per me è l'avvento dell'hockey. Nel corso del tempo, questo (naturalmente) è passato dal tifare per le mie squadre preferite alle scommesse (legali) sulle partite di hockey per passare il tempo.

L'hockey non è così popolare come sport come il football americano e, a questo proposito, non puoi scommettere sull'hockey nello stesso modo in cui scommetti sul calcio e ti aspetti di avere successo. Con uno sport meno popolare come l'hockey, devi guardare a sistemi redditizi come squadre riposate contro squadre stanche come descrive questo articolo :

Quando un favorito sta uscendo da due a quattro giorni di riposo e l'avversario è alla seconda notte di un back-to-back, il favorito riposato è passato da 1.834 a 1.133 (62%) dal 2005, secondo Bet Labs Sports

Usando queste informazioni, sapevo di poter usare le mie capacità di dati per rispondere a due domande importanti per qualsiasi incontro NHL (National Hockey League):

  • Quale squadra è relativamente più riposata dell'altra?
  • Quale squadra sta giocando una partita back to back?

Il primo ordine del giorno è stato trovare un'origine dati ed estrarre i dati di cui avevo bisogno . Fortunatamente, ho trovato questo progetto che mantiene un'API di dati NHL accessibili al pubblico. La mia interazione con l' endpoint dell'API Schedule è la seguente:

#get current date
datex = '2023-10-01'
#request
res=requests.get(f"https://statsapi.web.nhl.com/api/v1/schedule?startDate=2022-10-01&endDate={datex}").json()
#normalize
data = json_normalize(res,record_path= ['dates','games'])
data = data.drop_duplicates()
#get away team
df1 = data[['gameDate','teams.away.team.name']]
df1 = df1.rename(columns={"teams.away.team.name": "team"})
#get home team
df2 = data[['gameDate','teams.home.team.name']]
df2 = df2.rename(columns={"teams.home.team.name": "team"})
#concat  results
df3 = pd.concat([df1,df2],axis='rows', join='inner')
df3['datex'] = pd.to_datetime(df3['gameDate']).dt.date
df3['datex'] = df3.datex.astype('datetime64[ns]')
#clean and sort results
df3 = df3.drop('gameDate', axis=1)
df3 = df3.drop_duplicates()
df3 = df3.sort_values(by=['datex'])

Trasformare

Per quantificare il riposo rispetto alla stanchezza, ho deciso di calcolare due metriche:

roll7 — un numero continuo di 7 giorni di matchup per squadra . Ciò richiede (1) la creazione di un dataframe di tutte le date possibili all'interno di un intervallo, (2) un dataframe di tutte le date di gioco programmate per ogni squadra, (3) unendole insieme e (4) utilizzando un semplice .rolling().count( ) per creare la metrica finale.

back2back — Dopo aver ordinato gli incontri programmati per data, per squadra, sono stato in grado di utilizzare la funzione .shift() per creare un confronto riga su riga per quantificare la metrica dei giochi back to back (back2back) .

Tutto questo è stato fatto utilizzando un ciclo su un elenco di tutti i team NHL, che alla fine ha aggiunto i risultati per ogni squadra a un dataframe conglomerato. Il codice per questa prima trasformazione è il seguente:

#get unique list of teams 
teamlist = df3['team'].tolist()
teamlist = list( dict.fromkeys(teamlist) )
dfmain = pd.DataFrame(columns=['datex','team','roll7','back2back'])

for team in teamlist:
    #isolate to one team
    tmpdf1 = df3.loc[df3['team'] == team]
    #format date
    tmpdf1['datex'] = pd.to_datetime(tmpdf1['datex']).dt.date
    tmpdf1['datex'] = tmpdf1.datex.astype('datetime64[ns]')
    # create all dates dataframe
    tmpdf2 = pd.DataFrame({'datex':pd.date_range(start="2022-10-01",end=datex)})
    #format date
    tmpdf2['datex'] = tmpdf2.datex.astype('datetime64[ns]')
    #merge
    tmpdf3 = tmpdf2.merge(tmpdf1, on='datex', how = 'left')
    #create stats
    tmpdf3['roll7'] = tmpdf3['team'].rolling(7).count()
    tmpdf3['offset'] = tmpdf3.team.shift(1)
    tmpdf3['back2back'] = np.where(tmpdf3.team == tmpdf3.offset, 1, 0)
    #limit columns
    tmpdf3 = tmpdf3[['datex','team','roll7','back2back']]
    #concat
    dfmain = pd.concat([dfmain,tmpdf3], axis='rows')

#create new clean dataframe
dfx = data[['gamePk','gameDate','teams.away.team.name','teams.home.team.name']]
dfx = dfx.drop_duplicates()

#clean date
dfx['datex'] = pd.to_datetime(dfx['gameDate']).dt.date
dfx['datex'] = dfx.datex.astype('datetime64[ns]')
#rename columns
dfx = dfx.rename(columns={"teams.away.team.name": "away_team","teams.home.team.name": "home_team"})
#drop columns
dfx = dfx.drop('gameDate', axis=1)

#merge away data
dfy = pd.merge(dfx, dfmain, left_on=  ['away_team', 'datex'],
                   right_on= ['team', 'datex'], 
                   how = 'left')
#clean away data
dfy = dfy.rename(columns={"roll7": "away_roll7","back2back": "away_back2back"})
dfy = dfy.drop('team', axis=1)

#merge home data
dfy = pd.merge(dfy, dfmain, left_on=  ['home_team', 'datex'],
                   right_on= ['team', 'datex'], 
                   how = 'left')
#clean home data
dfy = dfy.rename(columns={"roll7": "home_roll7","back2back": "home_back2back"})
dfy = dfy.drop('team', axis=1)

#get other stats
dfz = data[['gamePk','teams.away.score','teams.away.leagueRecord.wins','teams.away.leagueRecord.losses','teams.home.score','teams.home.leagueRecord.wins','teams.home.leagueRecord.losses']]
#merge other stats
dfy = pd.merge(dfy, dfz, left_on=  ['gamePk'],
                   right_on= ['gamePk'], 
                   how = 'left')
#cleanup column names
dfy = dfy.rename(columns={"teams.away.score": "away_score","teams.home.score": "home_score","teams.away.leagueRecord.wins":"away_wins","teams.home.leagueRecord.wins":"home_wins",
                          "teams.away.leagueRecord.losses":"away_loss","teams.home.leagueRecord.losses":"home_loss",})

      
                
BEFORE
AFTER

def roll7(row):
    if row['away_roll7'] > row['home_roll7']:
        val = 'home'
    elif row['away_roll7'] < row['home_roll7']:
        val = 'away'
    else:
        val = ''
    return val

dfy['roll7_adv'] = dfy.apply(roll7, axis=1)

def back2back(row):
    if row['away_back2back'] > row['home_back2back']:
        val = 'home'
    elif row['away_back2back'] < row['home_back2back']:
        val = 'away'
    else:
        val = ''
    return val

dfy['back2back_adv'] = dfy.apply(back2back, axis=1)

      
                

Distribuisci

La maggior parte delle volte sarei più che felice di fermarmi qui. Potrei eseguire questo codice con un notebook Jupyter ogni giorno e utilizzare il dataframe successivo per prendere decisioni informate. Tuttavia, volevo provare a creare un'app S treamlit in modo da poter condividere queste informazioni con il resto del mondo!

Se non hai familiarità con Streamlit, è uno strumento che ti consente di creare semplici applicazioni di dati utilizzando Python. Vale anche la pena ricordare che Streamlit è stato acquisito da Snowflake nel 2022, quindi credo fermamente che diventerà un elemento centrale dello stack moderno andando avanti.

Di seguito è riportato il codice Streamlit che ho utilizzato per creare e distribuire la mia app utilizzando il cloud Streamlit. Il codice specifico di Streamlit è relativamente semplice e mi consente di visualizzare il dataframe finale e creare un filtro dell'intervallo di date interattivo in modo che l'utente finale possa scegliere diversi intervalli di tempo da analizzare.

#start streamlit template
# Sidebar - title & filters
st.sidebar.markdown('### Data Filters')
start_date, end_date = st.sidebar.date_input('Choose a date range :', [datetime.today(), datetime.today()+dt.timedelta(days=7)])
#create filter mask
mask = (dfy['datex'] > start_date) & (dfy['datex'] <= end_date)
#apply mask
dfstl = dfy.loc[mask]
# Main
st.title(f"NHL Systems")
# Commentary
st.markdown('Inspiration https://www.vsin.com/advantage-nhl-rest-vs-tired-matchups/')
# Detailed Commentary
rvt = "Rest vs Tired here is expressed as **roll7_adv**. Whichever team is mentioned here has played fewer games on a rolling 7 day basis"
b2b = "We also included a **back2back_adv** metric which indicates which team is playing a back to back game. For instance, if home is the value of back2back_adv, this means the away team played last night and is coming into a back to back contest"
st.markdown(rvt)
st.markdown(b2b)
# Dataframe
st.markdown('### Dataframe')
st.dataframe(dfstl.sort_values('datex',
             ascending=False).reset_index(drop=True),1000,1000)

      
                
The final product — a working Streamlit app hosted on Streamlit cloud!!!

Lavoro con i dati sportivi in ​​molti dei miei progetti e volevo dedicare del tempo a spiegare perché utilizzo questo tipo di dati. La risposta più ovvia è che mi piacciono gli sport, ma le mie ragioni vanno ben oltre.

  1. I dati sportivi sono di facile accesso.

2. I dati sportivi sono dati viventi.

Nel caso della NHL, le partite vengono praticamente giocate su base giornaliera da ottobre a giugno. Ciò aggiunge un elemento "vivo" ai dati, poiché le partite vengono vinte/perse, i gol vengono segnati e ogni tipo di statistica di squadra/giocatore viene generata ogni notte. Questo imita molto i sistemi transazionali e altri dati del mondo reale, dove dimensioni e fatti in rapida evoluzione devono essere spiegati e analizzati.

3. Le tecniche utilizzate per creare prodotti di dati sportivi possono essere applicate a qualsiasi dominio.

In questo caso specifico, ho utilizzato i dati sportivi per creare nuove funzionalità di dati che alimentano un dashboard interattivo per utenti finali istruiti. Puoi sostituire i dati sportivi che ho usato con dati finanziari, dati sanitari, dati sui trasporti, dati sui clienti, dati sui prodotti e otterresti un risultato simile. Puoi utilizzare i dati sportivi per creare modelli di regressione, classificazione e deep learning senza alcun rischio. Puoi utilizzare i dati sportivi per modellare e gestire il tuo data warehouse e quindi portare queste lezioni nel mondo reale. Se puoi farlo con i dati sportivi, puoi farlo con QUALSIASI DATO!