Pertimbangkan lingkaran terbesar yang dapat ditampung dalam kotak mulai dari $\mathbb{R}^2$ di atas $[-1, 1]^2$. Lingkaran memiliki jari-jari 1, dan luas $\pi$. Persegi memiliki luas $2^2$ = 4. Rasio antara area mereka adalah $\pi/4$
Kita dapat memperkirakan nilai π menggunakan metode Monte Carlo menggunakan prosedur berikut
- gambar persegi di atas $[-1,1]^2$ lalu gambar lingkaran terbesar yang muat di dalam persegi
- secara acak menyebarkan sejumlah besar $N$ butir beras di atas alun-alun
- hitung berapa butir yang jatuh di dalam lingkaran
- hitungan dibagi dengan $N$ dan dikalikan dengan 4 adalah perkiraan $\pi$
Kita dapat mensimulasikan prosedur ini dalam NumPy dengan menggambar angka acak dari distribusi seragam antara -1 dan 1 untuk mewakili posisi $x$ dan $y$ butir beras kita, dan memeriksa apakah titik tersebut berada di dalam lingkaran menggunakan teorema Pythagoras. Prosedur ini merupakan adaptasi dari apa yang disebut masalah jarum Buffon, setelah ahli matematika Prancis abad ke-18, Count of Button. Itu milik topik yang disebut probabilitas geometris
Untuk tutorial hari ini, kita akan menjelajahi dasar-dasar di balik algoritme komputasi populer yang disebut simulasi Monte Carlo dan bagaimana Anda dapat menerapkannya ke data sepak bola menggunakan Python
Di akhir tutorial ini, Anda akan mendapatkan ide bagus tentang hal berikut
- Apa itu simulasi Monte Carlo
- Cara melakukan simulasi dasar Monte Carlo dengan Python
- Bagaimana Anda bisa menerapkan simulasi Monte Carlo ke data sepakbola
Apa itu simulasi Monte Carlo?
Simulasi Monte Carlo pada intinya adalah teknik sederhana yang menggunakan pengambilan sampel acak untuk mensimulasikan fenomena dunia nyata
Intinya, metode Monte Carlo digunakan ketika kita tertarik pada masalah yang pada prinsipnya bisa deterministik tetapi sulit untuk mengetahui jawaban pastinya. Oleh karena itu, kami memanfaatkan keacakan (dan komputer) untuk memperkirakan jawaban kami
Mari kita mulai dengan contoh sederhana untuk mengilustrasikan ide tersebut
Bayangkan teman Anda Bob menawarkan Anda untuk berpartisipasi dalam permainan di mana dia melempar sepuluh dadu secara bersamaan. Jika jumlah dadu antara empat puluh dan lima puluh, dia memberi Anda sepuluh dolar;
Haruskah Anda memainkan game ini?
Untuk memutuskan apakah akan bermain, pertama-tama Anda harus mencari tahu kemungkinan memenangkan sejumlah uang. Ya, Anda bisa menghitung kemungkinan memenangkan permainan dengan tangan;
Di sinilah metode Monte Carlo bisa berguna. Mari kita lanjutkan dan selesaikan masalah menggunakan Python
Jika Anda tidak tahu, baru-baru ini saya meluncurkan program keanggotaan berbayar di mana komunitas dapat mendukung peningkatan dan konten yang tersedia di situs web. Salah satu keuntungan menjadi anggota adalah Anda mendapatkan akses awal ke tutorial – jadi jika Anda secara konsisten mendapatkan manfaat dari situs ini, saya akan sangat menghargai jika Anda mempertimbangkan untuk bergabung dengan kami
Pelajari lebih lanjut tentang keanggotaan
🗒️
Jika Anda bukan anggota berbayar, jangan khawatir, postingan ini akan tersedia untuk umum pada tanggal 18 November 2022
🤖
Semua kode yang digunakan dalam tutorial ini dan pustaka yang diperlukan dapat ditemukan di buku catatan yang disertakan
Mari kita mulai dengan menulis program Python yang mereplikasi kejadian kita – lemparan sepuluh dadu acak
def throw_and_sum_n_dice(n): ''' This function throws n dice and sums the results ''' total = 0 for i in range(0,n): throw = randint(1,6) total += throw return totalManis, sekarang setelah kita menentukan fungsi itu, kita dapat menulis beberapa kode yang memanggil fungsi ini jutaan kali dan kemudian menghitung seberapa sering kita akan memenangkan sejumlah uang
iterations = 1000000 winning_occurances = 0 for i in range(iterations): total = throw_and_sum_n_dice(10) if total >= 40 and total <= 50: winning_occurances += 1 print(f'We would\'ve won money {winning_occurances:,.0f} times -- a.k.a. {winning_occurances/iterations:.3%} of the time.') >> We would've won money 202,691 times -- a.k.a. 20.269% of the time.Menarik. Sepertinya kita memiliki peluang 20% untuk memenangkan sepuluh dolar dan peluang 80% untuk kehilangan dua dolar, yang berarti permainan ini sedikit menguntungkan kita dalam hal nilai yang diharapkan.
\[\begin{align*} \text{Nilai yang diharapkan} &= 10 \times 20\% -2 \times 80\%\\ &= 0. 39 \end{sejajarkan*}\](Sepertinya Bob tidak tahu tentang simulasi Monte Carlo 😉)
Hal penting yang perlu diperhatikan di sini adalah bahwa meskipun kami memiliki nilai harapan yang positif, kemungkinan besar jika kami hanya bermain sekali, kami akan kehilangan sejumlah uang.
Jadi mari kita bayangkan skenario alternatif di mana kami meyakinkan Bob untuk mengizinkan kami memainkan game ini lima puluh kali berturut-turut, dan di akhir putaran, kami membagikan pembayaran. Berapa banyak jalan yang akan membawa kita pada keuntungan?
Ayo gunakan Monte Carlo sekali lagi untuk mendapatkan hasilnya
Kita mulai dengan mendefinisikan fungsi kita yang menghitung pembayaran total setelah \(k\) lemparan
def compute_profit_path(k, n): ''' This function returns the porfit (or loss) path at the end of k iterations of the game, and where we throw n dice. ''' total_profit = 0 for i in range(k): total = throw_and_sum_n_dice(n) if total >= 40 and total <= 50: total_profit += 10 else: total_profit += -2 return total_profitKemudian, kami melakukan Monte Carlo dengan mensimulasikan seratus ribu jalur alternatif dan menghitung beberapa statistik deskriptif dasar dengan hasilnya
iterations = 100000 total_profits = [] for i in range(iterations): total_profits.append(compute_profit_path(50,10))_Mari kita lihat apa yang bisa terjadi jika kita memainkan game ini
- Kami akan memenangkan setidaknya sejumlah uang 71. 7% dari waktu
- Simulasi kami memperkirakan bahwa jumlah maksimum yang dapat kami menangkan adalah 152 dolar
- Simulasi kami memperkirakan bahwa jumlah maksimum yang dapat kami hilangkan adalah 76 dolar
- Hasil yang paling umum adalah kita memenangkan 20 dolar, yang terjadi 14. 8% dari waktu
- Kami akan memenangkan lebih dari 10 dolar dalam 58. 0% dari skenario
- Kami akan kehilangan lebih dari 10 dolar dalam 17. 0% dari skenario
Lumayan untuk kerja seharian
📝 Rekap
Simulasi Monte Carlo adalah algoritma komputasi yang memanfaatkan keacakan untuk memperkirakan solusi untuk masalah yang bersifat deterministik atau yang memiliki interpretasi probabilistik
Untuk melakukan simulasi Monte Carlo, Anda harus
- Tentukan properti statistik input eksperimen Anda. Dalam contoh dadu kita, ini adalah probabilitas bahwa sisi sepuluh dadu kita akan berjumlah angka tertentu
- Lakukan banyak pengulangan untuk eksperimen Anda – jutaan lemparan sepuluh dadu kami
- Gunakan hasil pengulangan untuk memperkirakan jawaban dari masalah – dalam kasus kita, apakah kita memiliki kesempatan untuk memenangkan sejumlah uang dari Bob
Sekarang setelah kita membahasnya, mari lanjutkan dan terapkan konsep ini ke beberapa data sepak bola
Menerapkan Simulasi Monte Carlo ke Data Sepak Bola
Ada aplikasi yang tak terhitung jumlahnya untuk simulasi Monte Carlo dalam ruang sepak bola. Dalam tutorial ini, kita akan fokus pada pemecahan masalah sederhana secara mendetail sehingga Anda dapat memahami dasar-dasarnya dan kemudian menerapkan konsep ini ke dalam analisis Anda sendiri
Masalah yang akan kita fokuskan terdiri dari memperkirakan probabilitas bahwa tim tertentu menang, kalah, atau seri dalam pertandingan tertentu berdasarkan kualitas peluang yang diciptakan – hasilnya harus memberikan penilaian yang baik tentang kinerja tim, dan seberapa beruntungnya
💡
Perhatikan bahwa karena kami mensimulasikan skenario di mana peluang telah dibuat, solusi kami tidak memiliki nilai prediktif. Ini hanya memberikan penilaian pasca-pertandingan kinerja tim
Mari kita mulai dengan mendefinisikan masalahnya
Pertama-tama, mari kita asumsikan bahwa xG (gol yang diharapkan) dari sebuah tembakan sama dengan probabilitas bahwa tembakan itu menghasilkan gol. Kemudian, untuk pertandingan apa pun, kami dapat mengulangi setiap tembakan dan melakukan uji coba Bernoulli untuk mensimulasikan apakah tembakan tersebut menghasilkan gol atau meleset.
🎯
Uji coba Bernoulli pada dasarnya adalah "balik koin", dengan parameter p yang menunjukkan probabilitas keberhasilan. Dalam kasus kita, sukses sama dengan gol, dan p sama dengan xG tembakan
Untuk penyederhanaan, kami berasumsi bahwa semua bidikan tidak bergantung satu sama lain. Ini penting – dan mungkin tidak sepenuhnya benar – karena memungkinkan kami menyimpulkan hasil eksperimen tanpa harus mengkhawatirkan korelasi antara peristiwa
Dengan memutar ulang simulasi ini beberapa kali, kami kemudian dapat menggunakan hasilnya untuk memperkirakan probabilitas setiap hasil dalam pertandingan tersebut
Kedengarannya bagus?
Mari kita uji dengan beberapa data aktual, dan simulasikan probabilitas setiap pertandingan selama penyisihan grup untuk melihat hasil mana yang paling tidak mungkin
Memuat data
Saya telah menyediakan kumpulan data yang berisi semua bidikan yang diambil di babak Grup Liga Champions UEFA
df = pd.read_csv('data/ucl_shot_data.csv', index_col=0) # -- We filter own goals since they have no xG df = df[df['is_own_goal'] == False] df.head()| | shot_id | event_type | team_id | player_id | player_name | shot_x | shot_y | min | min_added | is_blocked | is_on_target | blocked_x | blocked_y | goal_crossed_y | goal_crossed_z | xG | xGOT | shot_type | situation | period | is_own_goal | on_goal_x | on_goal_y | match_id | short_name | team_name | |---:|-----------:|:-------------|----------:|------------:|:-------------------|---------:|---------:|------:|------------:|:-------------|:---------------|------------:|------------:|-----------------:|-----------------:|----------:|---------:|:------------|:------------|:----------|:--------------|------------:|------------:|-----------:|-------------:|:-----------------| | 0 | 2456730363 | Miss | 178475 | 450980 | Timo Werner | 98.0526 | 48.1345 | 3 | nan | False | False | nan | nan | 41.5991 | 3.13352 | 0.0951161 | nan | LeftFoot | FastBreak | FirstHalf | False | 0 | 36.5723 | 4010151 | nan | RB Leipzig | | 1 | 2456736539 | AttemptSaved | 178475 | 704523 | Christopher Nkunku | 90.2 | 37.2025 | 12 | nan | True | True | 93 | 36.8975 | 35.4488 | 1.22 | 0.0719939 | nan | RightFoot | FromCorner | FirstHalf | False | 27.753 | 14.239 | 4010151 | nan | RB Leipzig | | 2 | 2456736981 | AttemptSaved | 178475 | 704523 | Christopher Nkunku | 97.9561 | 36.9738 | 12 | nan | False | True | 103.293 | 35.22 | 34.9913 | 0.488 | 0.138567 | 0.091 | RightFoot | RegularPlay | FirstHalf | False | 33.1994 | 5.69561 | 4010151 | nan | RB Leipzig | | 3 | 2456739727 | Goal | 9728 | 620543 | Maryan Shved | 75.9529 | 40.8057 | 16 | nan | False | True | nan | nan | 31.0263 | 0.160526 | 0.133662 | 0.5745 | LeftFoot | RegularPlay | FirstHalf | False | 80.4018 | 1.87356 | 4010151 | nan | Shakhtar Donetsk | | 4 | 2456747617 | Miss | 178475 | 388523 | André Silva | 81.3587 | 37.2025 | 29 | nan | False | False | nan | nan | 24.3496 | 2.13821 | 0.0646847 | nan | RightFoot | RegularPlay | FirstHalf | False | 229.773 | 24.9558 | 4010151 | nan | RB Leipzig |Selain data tembakan, kami juga memiliki kumpulan data info pertandingan yang berisi semua informasi terkait pertandingan. Ini akan berguna untuk menilai dan mengulangi hasil kami
match_data = pd.read_csv('data/match_info_data.csv', index_col=0) match_data.head()_| | date | match_id | home_team_id | home_team_name | away_team_id | away_team_name | home_team_score | away_team_score | finished | cancelled | result | league_id | league_name | |---:|:--------------------|-----------:|---------------:|:-----------------|---------------:|:-----------------|------------------:|------------------:|:-----------|:------------|:---------|------------:|:-----------------| | 0 | 2022-09-07 18:45:00 | 4010118 | 8593 | Ajax | 8548 | Rangers | 4 | 0 | True | False | Home | 42 | Champions League | | 1 | 2022-09-07 21:00:00 | 4010119 | 9875 | Napoli | 8650 | Liverpool | 4 | 1 | True | False | Home | 42 | Champions League | | 2 | 2022-09-07 21:00:00 | 4010138 | 9906 | Atletico Madrid | 9773 | FC Porto | 2 | 1 | True | False | Home | 42 | Champions League | | 3 | 2022-09-07 21:00:00 | 4010139 | 8342 | Club Brugge | 8178 | Leverkusen | 1 | 0 | True | False | Home | 42 | Champions League | | 4 | 2022-09-07 21:00:00 | 4010163 | 8634 | Barcelona | 6033 | Viktoria Plzen | 5 | 1 | True | False | Home | 42 | Champions League |Simulasi pertandingan
Ok, sekarang saatnya untuk turun ke seluk beluk. Menulis kode yang mensimulasikan percobaan acak tunggal dari kecocokan kita
Perhatikan baik-baik fungsi di bawah ini, dan coba pahami fungsinya. Namun, jangan khawatir jika kurang jelas, kami akan menjelaskannya sebentar lagi
def simulate_match_on_shots(match_id, shot_df, home_team_id, away_team_id): ''' This function takes a match ID and simulates an outcome based on the shots taken by each team. ''' shots = shot_df[shot_df['match_id'] == match_id] shots_home = shots[shots['team_id'] == home_team_id] shots_away = shots[shots['team_id'] == away_team_id] home_goals = 0 # If shots == 0 then there's no sampling if shots_home['xG'].shape[0] > 0: for shot in shots_home['xG']: # Sample a number between 0 and 1 prob = np.random.random() # If the probability sampled is less than the xG then it's a goal. if prob < shot: home_goals += 1 away_goals = 0 if shots_away['xG'].shape[0] > 0: for shot in shots_away['xG']: # Sample a number between 0 and 1 prob = np.random.random() # If the probability sampled is less than the xG then it's a goal. if prob < shot: away_goals += 1 return {'home_goals':home_goals, 'away_goals':away_goals}_Inilah yang dilakukan kode
- Kami pertama-tama memfilter bidikan kami iterations = 1000000 winning_occurances = 0 for i in range(iterations): total = throw_and_sum_n_dice(10) if total >= 40 and total <= 50: winning_occurances += 1 print(f'We would\'ve won money {winning_occurances:,.0f} times -- a.k.a. {winning_occurances/iterations:.3%} of the time.') _9 dan membaginya menjadi dua. Satu untuk tim tuan rumah, dan satu untuk tim tandang
- Selanjutnya, kami mengulangi setiap tembakan yang dilakukan oleh masing-masing pihak dan melempar koin bias untuk menghitung kesempatan di mana tembakan diambil sampelnya secara acak sebagai gol. Ini dilakukan dengan menghasilkan angka antara 0 dan 1; . Perhatikan bagaimana semakin tinggi xG, semakin tinggi peluang hasilnya menjadi sasaran
- Kami meringkas hasil di setiap iterasi dan mengembalikan skor acak
Memperkirakan probabilitas
Besar. Sekarang kita dapat membuat skor acak, saatnya melakukan simulasi Monte Carlo
Untuk melakukan ini, kami menghasilkan sejumlah besar garis skor acak dan menghitung berapa kali setiap hasil (kemenangan kandang, seri, dan kemenangan tandang) terjadi. Kemudian, kami membagi angka tersebut dengan total garis skor yang dihasilkan
Sederhana, bukan?
Ayo tulis kodenya
iterations = 1000000 winning_occurances = 0 for i in range(iterations): total = throw_and_sum_n_dice(10) if total >= 40 and total <= 50: winning_occurances += 1 print(f'We would\'ve won money {winning_occurances:,.0f} times -- a.k.a. {winning_occurances/iterations:.3%} of the time.') _0Sebagai contoh, mari kita lakukan 10.000 simulasi pada pertandingan Ajax vs. Pertandingan Rangers (>> We would've won money 202,691 times -- a.k.a. 20.269% of the time._0 )
iterations = 1000000 winning_occurances = 0 for i in range(iterations): total = throw_and_sum_n_dice(10) if total >= 40 and total <= 50: winning_occurances += 1 print(f'We would\'ve won money {winning_occurances:,.0f} times -- a.k.a. {winning_occurances/iterations:.3%} of the time.') 1iterations = 1000000 winning_occurances = 0 for i in range(iterations): total = throw_and_sum_n_dice(10) if total >= 40 and total <= 50: winning_occurances += 1 print(f'We would\'ve won money {winning_occurances:,.0f} times -- a.k.a. {winning_occurances/iterations:.3%} of the time.') 2Keren. Sepertinya pertandingan itu telah diputar ulang 10.000 kali Ajax akan menang 88% dari waktu – mereka benar-benar memenangkan pertandingan itu 4 - 0
Membungkus barang
Sekarang kita memiliki fungsi yang memutar ulang pertandingan k kali dan mengembalikan perkiraan probabilitas, mari kita simulasikan semua pertandingan di babak grup untuk menemukan hasil yang paling tidak mungkin
(ini bisa memakan waktu beberapa menit, jadi ini alasan yang bagus untuk mengambil secangkir kopi ☕️)
iterations = 1000000 winning_occurances = 0 for i in range(iterations): total = throw_and_sum_n_dice(10) if total >= 40 and total <= 50: winning_occurances += 1 print(f'We would\'ve won money {winning_occurances:,.0f} times -- a.k.a. {winning_occurances/iterations:.3%} of the time.') _3Setelah selesai, kami mengonversi hasil kami menjadi iterations = 1000000 winning_occurances = 0 for i in range(iterations): total = throw_and_sum_n_dice(10) if total >= 40 and total <= 50: winning_occurances += 1 print(f'We would\'ve won money {winning_occurances:,.0f} times -- a.k.a. {winning_occurances/iterations:.3%} of the time.') 9 dan menggabungkannya dengan kumpulan data >> We would've won money 202,691 times -- a.k.a. 20.269% of the time.2 kami
iterations = 1000000 winning_occurances = 0 for i in range(iterations): total = throw_and_sum_n_dice(10) if total >= 40 and total <= 50: winning_occurances += 1 print(f'We would\'ve won money {winning_occurances:,.0f} times -- a.k.a. {winning_occurances/iterations:.3%} of the time.') _4Outputnya akan terlihat seperti ini
iterations = 1000000 winning_occurances = 0 for i in range(iterations): total = throw_and_sum_n_dice(10) if total >= 40 and total <= 50: winning_occurances += 1 print(f'We would\'ve won money {winning_occurances:,.0f} times -- a.k.a. {winning_occurances/iterations:.3%} of the time.') _5Menilai hasil
Untuk mengevaluasi kinerja setiap tim, kami akan melakukan uji statistik untuk melihat hasil mana yang paling tidak mungkin. Untuk ini, kami akan menggunakan fungsi Brier Score yang dirancang untuk menghitung keakuratan prediksi probabilistik. Meskipun kami tidak memperkirakan apa pun di sini, ini akan menunjukkan hasil mana yang paling tidak mungkin
Definisi BS adalah
\[\text{BS} = (P_h - o_h)^2 + (P_d - o_d)^2 + (P_a - o_a)^2\]
di mana. \(P\) masing-masing menunjukkan probabilitas kemenangan kandang, seri, dan kemenangan tandang;
Inilah kode untuk mencapainya
iterations = 1000000 winning_occurances = 0 for i in range(iterations): total = throw_and_sum_n_dice(10) if total >= 40 and total <= 50: winning_occurances += 1 print(f'We would\'ve won money {winning_occurances:,.0f} times -- a.k.a. {winning_occurances/iterations:.3%} of the time.') 6iterations = 1000000 winning_occurances = 0 for i in range(iterations): total = throw_and_sum_n_dice(10) if total >= 40 and total <= 50: winning_occurances += 1 print(f'We would\'ve won money {winning_occurances:,.0f} times -- a.k.a. {winning_occurances/iterations:.3%} of the time.') 7Akhirnya, kami melihat dua puluh hasil yang paling tidak mungkin
iterations = 1000000 winning_occurances = 0 for i in range(iterations): total = throw_and_sum_n_dice(10) if total >= 40 and total <= 50: winning_occurances += 1 print(f'We would\'ve won money {winning_occurances:,.0f} times -- a.k.a. {winning_occurances/iterations:.3%} of the time.') _8WOW. Empat dari pertandingan Atlético Madrid masuk dalam tujuh pertandingan teratas yang paling tidak mungkin – meskipun beberapa di antaranya menguntungkan mereka
Jadi begitulah, itulah salah satu contoh bagaimana Anda dapat menerapkan simulasi Monte Carlo ke data sepakbola. Semoga Anda mempelajari sesuatu yang baru dari tutorial ini dan saya akan menyusul Anda nanti 😎
📝 Latihan Opsional
Jika Anda ingin menguji keterampilan Anda, saya sarankan Anda memperluas contoh di atas dan mensimulasikan peluang setiap tim untuk lolos ke babak enam belas